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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ npx -y -p @stateset/cli stateset-mcp --db ./store.db --profile core # focused s
npx -y -p @stateset/cli stateset-mcp-http # Streamable HTTP, protocol 2026-07-28, stateless
```

Building the agent yourself? `@stateset/embedded` ships adapters for the
Vercel AI SDK, LangChain, OpenAI tool calls, and any custom loop — see the
[guardrails quickstarts](examples/agents/README.md) where an agent tries to
over-refund and the engine answers with a sealed
`commerce.refund.exceeds_captured` receipt instead.

On Omarchy, install the native commerce widget, Super-key menu actions, and
project-local MCP configuration in one command:

Expand Down
11 changes: 10 additions & 1 deletion bindings/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@
"embedded",
"sqlite",
"rust",
"napi"
"napi",
"ai-agents",
"agent-tools",
"llm",
"vercel-ai",
"langchain",
"openai",
"mcp",
"local-first",
"agentic-commerce"
],
"author": "StateSet <support@stateset.com>",
"license": "MIT OR Apache-2.0",
Expand Down
9 changes: 7 additions & 2 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@
"stripe",
"woocommerce",
"shopify",
"webhooks"
"webhooks",
"mcp-tools",
"ai-agents",
"agentic-commerce",
"model-context-protocol"
],
"author": "StateSet <support@stateset.com>",
"license": "MIT OR Apache-2.0",
Expand Down Expand Up @@ -233,5 +237,6 @@
},
"publishConfig": {
"access": "public"
}
},
"mcpName": "io.github.stateset/icommerce"
}
2 changes: 1 addition & 1 deletion cli/smithery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ startCommand:
commandFunction: |-
(config) => ({
command: 'npx',
args: ['-y', '-p', '@stateset/cli@latest', 'stateset-mcp', '--db', config.DB_PATH || './store.db'],
args: ['-y', '-p', '@stateset/cli@latest', 'stateset-mcp', '--db', config.DB_PATH || './store.db', '--profile', 'core'],
env: {}
})

Expand Down
20 changes: 20 additions & 0 deletions docs/src/ai-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ From the repo checkout, the examples under `examples/agents/` also run against
workspace modules directly, so the embedded path is smoke-tested before
publish.

## Guardrails quickstarts

Four runnable examples show the property that distinguishes this engine: give
an agent real refund powers and it still cannot over-refund, because the
invariant is enforced inside the database transaction and comes back as a
sealed kernel receipt with the stable code `commerce.refund.exceeds_captured`.

```bash
node examples/agents/vercel-ai-guardrails.mjs # Vercel AI SDK
node examples/agents/langchain-guardrails.mjs # LangChain structured tools
node examples/agents/openai-guardrails.mjs # OpenAI tool calls
node examples/agents/custom-runtime-guardrails.mjs # any agent loop / Claude Agent SDK
```

Each runs in CI. See `examples/agents/README.md` for the pattern and the
[kernel execution](kernel-execution.md) chapter for production policy and
principal configuration. One note for agent authors: the adapter result's
top-level `success` means the tool call executed; branch on
`result.receipt.error_code` for the commercial outcome.

## Embedded Toolkit

The embedded toolkit gives your agent direct access to the full registry-generated commerce tool surface:
Expand Down
25 changes: 25 additions & 0 deletions examples/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@
Runnable examples showing how to embed the iCommerce engine inside agent
frameworks and agent-like runtimes.

## Guardrails Quickstarts — "an agent cannot over-refund"

Each quickstart gives an agent real refund powers through a governed kernel,
has it attempt to refund $250 of a $100 payment, and prints the sealed
receipt the engine returns instead: `commerce.refund.exceeds_captured`.
The guarantee is enforced inside the database transaction — not by the
prompt, not by the framework.

| File | Framework surface |
|------|-------------------|
| `vercel-ai-guardrails.mjs` | Vercel AI SDK (`createVercelAITools`) |
| `langchain-guardrails.mjs` | LangChain structured tools (`createLangChainTools`) |
| `openai-guardrails.mjs` | OpenAI Responses/Agents tool calls (`executeOpenAIToolCall`) |
| `custom-runtime-guardrails.mjs` | Generic descriptors for any agent loop, incl. Claude Agent SDK (`createToolDescriptors`) |

```bash
node examples/agents/vercel-ai-guardrails.mjs
```

MCP clients (Claude Desktop, Claude Code, Cursor) get the same governed
tools with zero code: `npx -y -p @stateset/cli stateset-mcp --db ./store.db`.
The demo kernel in `guardrails-demo-helpers.mjs` is intentionally permissive;
production deployments load operator-owned policy/principal files
(`kernel/examples/strict-policy.json`, `strict-principal.json`).

The embedded toolkit examples run from a raw repo checkout and also work after
installing the published packages. They load published packages first and fall
back to workspace modules when you are developing inside this repository.
Expand Down
47 changes: 47 additions & 0 deletions examples/agents/custom-runtime-guardrails.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Custom runtime / Claude Agent SDK quickstart: generic tool descriptors
// ({ name, description, schema, execute }) work with any agent loop.
// Claude Desktop and Claude Code users can get the same tools with zero
// code via MCP: npx -y -p @stateset/cli stateset-mcp --db ./store.db
//
// Run: node examples/agents/custom-runtime-guardrails.mjs
import { isMain } from './x402-demo-helpers.mjs';
import { emitSummary } from './embedded-toolkit-runtime.mjs';
import { outcomeLine, receiptOutcome, setupGuardrailsScenario } from './guardrails-demo-helpers.mjs';

export async function runCustomRuntimeGuardrailsDemo({ logger = console } = {}) {
const { commerce, payment, toolkitOptions } = await setupGuardrailsScenario();
const { createToolDescriptors } = await import('../../bindings/node/generic.mjs');

const descriptors = createToolDescriptors(commerce, {
filter: ['create_refund'],
allowApply: true,
toolkitOptions,
});
const refundTool = descriptors.find((descriptor) => descriptor.name === 'create_refund');

const blocked = receiptOutcome(
await refundTool.execute({ paymentId: payment.id, amount: 250.0, reason: 'agent mistake' }),
);
const lines = [outcomeLine('Over-refund attempt', blocked)];

const allowed = receiptOutcome(
await refundTool.execute({ paymentId: payment.id, amount: 40.0, reason: 'customer request' }),
);
lines.push(outcomeLine('Legit $40 refund', allowed));

const summary = {
framework: 'custom-runtime',
overRefundBlocked: blocked.blocked,
invariantCode: blocked.code,
legitRefundExecuted: !allowed.blocked,
};
emitSummary(summary, lines, logger);
return summary;
}

if (isMain(import.meta)) {
runCustomRuntimeGuardrailsDemo().catch((error) => {
console.error(error);
process.exitCode = 1;
});
}
93 changes: 93 additions & 0 deletions examples/agents/guardrails-demo-helpers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Shared setup for the guardrails quickstarts: a paid order plus a
// demo-scoped governed kernel, so each framework example can show the
// engine refusing an over-refund with a sealed, machine-readable receipt.
import { loadEmbeddedToolkitRuntime } from './embedded-toolkit-runtime.mjs';

const REFUND_COMMAND = 'payments.create_refund';

/** Demo kernel config. In production, load operator-owned files instead — */
/** see kernel/examples/strict-policy.json and strict-principal.json. */
export function demoKernel() {
return {
strict: true,
storeId: 'store:demo',
policy: {
version: 'demo-v1',
commands: {
[REFUND_COMMAND]: {
required_capabilities: [REFUND_COMMAND],
requires_approval: false,
requires_tenant: true,
requires_store: true,
allowed_tenant_ids: ['tenant:demo'],
allowed_store_ids: ['store:demo'],
requires_agent_delegation: true,
requires_signed_authority: false,
},
},
trusted_authority_keys: {},
},
principal: {
id: 'agent:demo',
kind: 'agent',
tenant_id: 'tenant:demo',
delegated_by: 'user:operator',
capabilities: [REFUND_COMMAND],
},
};
}

/** In-memory store with one customer, one paid $100 order. */
export async function setupGuardrailsScenario() {
const runtime = await loadEmbeddedToolkitRuntime();
const commerce = new runtime.Commerce(':memory:');
const customer = await commerce.customers.create({
email: 'buyer@example.com',
firstName: 'Demo',
lastName: 'Buyer',
});
await commerce.inventory.createItem({ sku: 'WIDGET-1', name: 'Widget', initialQuantity: 10 });
const order = await commerce.orders.create({
customerId: customer.id,
items: [{ sku: 'WIDGET-1', name: 'Widget', quantity: 1, unitPrice: 100.0 }],
currency: 'USD',
});
const payment = await commerce.payments.create({
orderId: order.id,
customerId: customer.id,
amount: order.totalAmount,
currency: 'USD',
paymentMethod: 'credit_card',
});
await commerce.payments.markCompleted(payment.id);
const toolkitOptions = { capabilities: ['read:*', 'payments.*'], kernel: demoKernel() };
return { runtime, commerce, payment, toolkitOptions };
}

/** Normalize a toolkit result into { blocked, code, message, refundNumber }. */
export function receiptOutcome(result) {
if (typeof result === 'string') {
// LangChain-style tools return their observation as a JSON string.
result = JSON.parse(result);
}
const receipt = result?.result?.receipt || null;
if (receipt && receipt.error_code) {
return { blocked: true, code: receipt.error_code, message: receipt.error_message };
}
if (result?.status === 'error') {
return { blocked: true, code: null, message: result.error };
}
return {
blocked: false,
code: null,
message: null,
refundNumber: receipt?.aggregate_id || result?.result?.refundNumber || null,
};
}

export function outcomeLine(label, outcome) {
if (outcome.blocked) {
return `${label}: BLOCKED (${outcome.code || 'error'}) — ${outcome.message}`;
}
return `${label}: executed${outcome.refundNumber ? ` (${outcome.refundNumber})` : ''}`;
}
58 changes: 58 additions & 0 deletions examples/agents/langchain-guardrails.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// LangChain quickstart: the same guardrails demo through LangChain-style
// structured tools. The engine, not the prompt, guarantees the agent
// cannot over-refund.
//
// Run: node examples/agents/langchain-guardrails.mjs
import { isMain } from './x402-demo-helpers.mjs';
import { emitSummary } from './embedded-toolkit-runtime.mjs';
import { outcomeLine, receiptOutcome, setupGuardrailsScenario } from './guardrails-demo-helpers.mjs';

// Stand-in for `DynamicStructuredTool` from '@langchain/core/tools'; with
// LangChain installed, pass the real class instead.
class DynamicStructuredTool {
constructor(config) {
Object.assign(this, config);
}
invoke(input) {
return this.func(input);
}
}

export async function runLangChainGuardrailsDemo({ logger = console } = {}) {
const { commerce, payment, toolkitOptions } = await setupGuardrailsScenario();
const { createLangChainTools } = await import('../../bindings/node/langchain.mjs');

const tools = createLangChainTools(commerce, {
DynamicStructuredTool,
filter: ['create_refund'],
allowApply: true,
toolkitOptions,
});
const refundTool = tools.find((tool) => tool.name === 'create_refund');

const blocked = receiptOutcome(
await refundTool.invoke({ paymentId: payment.id, amount: 250.0, reason: 'agent mistake' }),
);
const lines = [outcomeLine('Over-refund attempt', blocked)];

const allowed = receiptOutcome(
await refundTool.invoke({ paymentId: payment.id, amount: 40.0, reason: 'customer request' }),
);
lines.push(outcomeLine('Legit $40 refund', allowed));

const summary = {
framework: 'langchain',
overRefundBlocked: blocked.blocked,
invariantCode: blocked.code,
legitRefundExecuted: !allowed.blocked,
};
emitSummary(summary, lines, logger);
return summary;
}

if (isMain(import.meta)) {
runLangChainGuardrailsDemo().catch((error) => {
console.error(error);
process.exitCode = 1;
});
}
50 changes: 50 additions & 0 deletions examples/agents/openai-guardrails.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// OpenAI (Responses/Agents) quickstart: the guardrails demo through
// OpenAI-format tool calls. The engine refuses the over-refund inside the
// database transaction and returns a sealed receipt with a stable code.
//
// Run: node examples/agents/openai-guardrails.mjs
import { isMain } from './x402-demo-helpers.mjs';
import { emitSummary } from './embedded-toolkit-runtime.mjs';
import { outcomeLine, receiptOutcome, setupGuardrailsScenario } from './guardrails-demo-helpers.mjs';

export async function runOpenAiGuardrailsDemo({ logger = console } = {}) {
const { commerce, payment, toolkitOptions } = await setupGuardrailsScenario();
const { executeOpenAIToolCall } = await import('../../bindings/node/openai.mjs');

const callRefund = async (callId, amount, reason) =>
executeOpenAIToolCall(
commerce,
{
call_id: callId,
function: {
name: 'create_refund',
arguments: JSON.stringify({ paymentId: payment.id, amount, reason }),
},
},
{ allowApply: true, toolkitOptions },
);

const blockedCall = await callRefund('call_1', 250.0, 'agent mistake');
const blocked = receiptOutcome(blockedCall.result);
const lines = [outcomeLine('Over-refund attempt', blocked)];

const allowedCall = await callRefund('call_2', 40.0, 'customer request');
const allowed = receiptOutcome(allowedCall.result);
lines.push(outcomeLine('Legit $40 refund', allowed));

const summary = {
framework: 'openai',
overRefundBlocked: blocked.blocked,
invariantCode: blocked.code,
legitRefundExecuted: !allowed.blocked,
};
emitSummary(summary, lines, logger);
return summary;
}

if (isMain(import.meta)) {
runOpenAiGuardrailsDemo().catch((error) => {
console.error(error);
process.exitCode = 1;
});
}
Loading
Loading