Build selling agents that connect your inventory to the AURA ecosystem. Beacons are server-side agents that register with AURA Core, handle buyer sessions, submit offers, and manage transaction lifecycles.
A Beacon is a selling agent that:
Beacons integrate into seller systems: e-commerce platforms, ERP systems, inventory management, booking systems, and marketplace integrations.
npm install @aura-labs/beacon
import { createBeacon } from '@aura-labs/beacon';
// Create beacon instance
const beacon = createBeacon({
externalId: 'my-store-001', // Your internal identifier
name: 'My Store',
description: 'Electronics retailer',
endpointUrl: 'https://mystore.com/webhook', // Receives transaction webhooks
capabilities: ['retail', 'shipping'],
coreUrl: 'https://core.aura-labs.io',
pollIntervalMs: 5000 // Poll every 5 seconds
});
// Register with AURA Core
const { beaconId } = await beacon.register();
console.log('Registered with beaconId:', beaconId);
// Handle incoming sessions
beacon.onSession(async (session, beacon) => {
const { sessionId, status, intent, constraints, createdAt } = session;
// intent.raw: original buyer text
// intent.parsed: structured intent { keywords }
// constraints: { categories } — buyer constraints are redacted for information asymmetry
// Use interpretIntent for smart catalog matching (recommended)
const result = await beacon.interpretIntent(intent.raw, myCatalog);
if (result.matches.length > 0) {
const topMatch = result.matches[0];
const offer = {
product: topMatch.item,
unitPrice: calculatePrice(topMatch.item),
quantity: 1,
currency: 'USD',
deliveryDate: calculateDelivery(),
terms: 'Standard terms apply'
};
await beacon.submitOffer(sessionId, offer);
}
});
// Apply business rules
beacon.registerPolicies({
minPrice: 10,
maxQuantityPerOrder: 100,
maxDeliveryDays: 30,
deliveryRegions: ['US', 'CA', 'UK']
});
// Optional: Pre-offer validation
beacon.beforeOffer(async (session, offer) => {
// Validate or modify offer before submission
if (offer.unitPrice < 5) {
throw new Error('Price too low');
}
return offer; // Return modified offer or undefined to block
});
// Track accepted offers
beacon.onOfferAccepted(async (transactionData) => {
console.log('Offer accepted, transaction:', transactionData.transactionId);
});
// Start polling for sessions
await beacon.startPolling();
// Graceful shutdown
process.on('SIGINT', async () => {
await beacon.stopPolling();
});
Beacons can interpret incoming buyer intents against their product catalog using @aura-labs/nlp — the shared NLP module used across the entire AURA pipeline. This is the recommended way to evaluate sessions, replacing simple string matching with structured category detection and keyword-scored catalog matching.
Intent interpretation is Layer 3 of the AURA three-layer NLP architecture:
Layer 1: @aura-labs/nlp — Shared module (category detection, completeness checking)
Layer 2: Scout SDK + Core — Completeness gate + authoritative parsing
Layer 3: Beacon SDK — Domain-specific catalog matching (this layer)
The Beacon performs presence detection only — it does not perform semantic authority over intent meaning. Core retains semantic authority per the Neutral Broker architecture (NEUTRAL_BROKER.md Property 1).
beacon.interpretIntent(intentText, catalog, options?)Interpret a buyer’s intent against your product catalog:
const catalog = [
{ name: 'Ergonomic Keyboard', sku: 'KB-ERG-001', category: 'keyboards', tags: ['ergonomic', 'wireless'] },
{ name: 'Gaming Mouse', sku: 'MS-GAM-001', category: 'mice', tags: ['gaming', 'wireless'] },
{ name: 'Ultra-Wide Monitor', sku: 'MN-UW-001', category: 'monitors', tags: ['ultrawide', '34-inch'] },
];
const result = await beacon.interpretIntent(
'I need 50 ergonomic keyboards under $5000',
catalog,
);
Returns:
{
matches: [
{ item: { name: 'Ergonomic Keyboard', ... }, score: 60, matchedOn: ['name', 'category', 'tags'] }
],
confidence: 0.45, // Overall intent completeness (0-1)
categories: { // Per-category presence detection from @aura-labs/nlp
what: { present: true },
how_many: { present: true },
how_much_cost: { present: true },
// ... 8 categories total (4 Tier 1 + 4 Tier 2)
},
suggestions: [ // Clarification questions for missing categories
'What specific features are you looking for?'
]
}
Parameters:
intentText — Raw intent text from the buyer (max 5000 chars)catalog — Array of catalog items with name, category, and/or tags fieldsoptions.provider — Optional LLM provider for model-based category detection (regex fallback when absent)Scoring: Items are scored by keyword overlap: +20 per name match, +15 per category match, +10 per tag match (capped at 100). Common stop words are filtered out. Partial matches are supported (e.g., “keyboard” matches “keyboards”).
Replace simple string matching with interpretIntent() for smarter offer decisions:
beacon.onSession(async (session) => {
const result = await beacon.interpretIntent(session.intent.raw, myCatalog);
if (result.matches.length > 0) {
const topMatch = result.matches[0];
await beacon.submitOffer(session.sessionId, {
product: topMatch.item,
unitPrice: topMatch.item.price,
quantity: 1,
});
}
});
For use outside the Beacon class (e.g., testing, pre-processing):
import { interpretIntent } from '@aura-labs/beacon';
const result = await interpretIntent('I need widgets', myCatalog);
interpretIntent() never throws on provider failure — if the LLM provider is unavailable, it falls back to regex-only category detection and still performs catalog matching. Provider errors are logged via activity events.
Beacons register once with AURA Core:
const beacon = createBeacon({
externalId: 'my-store-001', // Your internal identifier
name: 'My Store', // Display name
description: 'What you sell', // Brief description
endpointUrl: 'https://mystore.com/webhook', // Webhook receiver
capabilities: ['retail', 'shipping', 'drop-shipping'], // What you can do
coreUrl: 'https://core.aura-labs.io',
pollIntervalMs: 5000
});
const { beaconId } = await beacon.register();
// Returns beaconId (UUID) - use this to identify your beacon
// Future: Registration will also return Ed25519 keypair for request signing
What happens:
/v1/beacons/register with configbeaconIdSessions represent buyer intent. Your beacon polls for them:
beacon.onSession(async (session, beacon) => {
// session structure:
// {
// sessionId: "uuid",
// status: "market_forming",
// intent: {
// raw: "I need 500 industrial widgets under $50000",
// parsed: { keywords: ['need', '500', 'industrial', 'widgets'] }
// },
// constraints: {
// categories: ["widgets", "industrial"] // Buyer constraints are redacted
// },
// createdAt: "2026-03-03T10:00:00Z"
// }
// Use interpretIntent for smart matching (recommended)
const result = await beacon.interpretIntent(session.intent.raw, myCatalog);
if (result.matches.length > 0) {
await beacon.submitOffer(session.sessionId, buildOffer(result.matches[0]));
}
});
Polling:
// Automatically polls GET /v1/beacons/sessions every pollIntervalMs
await beacon.startPolling();
// Deduplicates sessions via internal Set
// Calls onSession handler for each new unique session
// Stops polling
await beacon.stopPolling();
Submit an offer to a session:
const offer = {
product: 'Wireless Headphones Pro', // Your product name/identifier
unitPrice: 89.99, // Price per unit
quantity: 1, // Quantity available
currency: 'USD',
deliveryDate: '2026-03-10', // When you can deliver
terms: 'Standard return policy applies', // Optional terms
metadata: { sku: 'SKU-123', color: 'black' } // Optional metadata
};
await beacon.submitOffer(sessionId, offer);
// POST to /v1/sessions/:sessionId/offers
// Returns offer confirmation
Validation flow:
beforeOffer validators (if registered)If any validator throws or returns undefined, offer is blocked.
Validate or modify offers before submission:
beacon.beforeOffer(async (session, offer) => {
// Validators run sequentially (chainable)
// Return modified offer, undefined to block, or throw to reject
// Example: Enforce minimum margin
if (offer.unitPrice < 50) {
throw new Error(`Price ${offer.unitPrice} below cost`);
}
// Example: Modify offer based on session
if (session.constraints.maxDeliveryDays < 3) {
offer.deliveryDate = calculateExpressDelivery();
offer.terms = 'Express delivery applies';
}
return offer; // Return to proceed
});
// Chain multiple validators
beacon
.beforeOffer(validateMinimumPrice)
.beforeOffer(validateInventory)
.beforeOffer(addDynamicDiscount);
Declare business rules. SDK auto-validates offers:
beacon.registerPolicies({
minPrice: 10, // Minimum unit price
maxQuantityPerOrder: 100, // Max units per offer
maxDeliveryDays: 30, // Maximum delivery time
deliveryRegions: ['US', 'CA', 'UK'] // Where you ship
});
What happens:
Called when a buyer commits to your offer:
beacon.onOfferAccepted(async (transactionData) => {
// {
// transactionId: "uuid",
// sessionId: "uuid",
// offer: { product, unitPrice, quantity, ... },
// committedAt: "2026-03-03T10:05:00Z"
// }
console.log('Order confirmed:', transactionData.transactionId);
// Reserve inventory, create order record, etc.
});
Called for any transaction status change:
beacon.onTransactionUpdate(async (event) => {
// {
// transactionId: "uuid",
// status: "committed" | "shipped" | "delivered" | "fulfilled" | "completed",
// timestamp: "2026-03-03T10:05:00Z",
// metadata: { ... }
// }
if (event.status === 'shipped') {
console.log('Order shipped:', event.transactionId);
}
});
Update fulfillment status as you process the order:
// When you ship the order
await beacon.updateFulfillment(transactionId, {
fulfillmentStatus: 'shipped',
fulfillmentReference: 'TRACK-123456', // Tracking number
metadata: { carrier: 'FedEx' }
});
// When delivered
await beacon.updateFulfillment(transactionId, {
fulfillmentStatus: 'delivered',
metadata: { deliveredAt: '2026-03-10T14:30:00Z' }
});
// GET transaction details
const transaction = await beacon.getTransaction(transactionId);
// {
// transactionId: "uuid",
// sessionId: "uuid",
// status: "delivered",
// offer: { ... },
// fulfillment: { status: "delivered", reference: "TRACK-123456" },
// createdAt: "2026-03-03T10:05:00Z",
// updatedAt: "2026-03-10T14:30:00Z"
// }
Transactions follow this state machine:
committed → shipped → delivered → fulfilled → completed
(your action) (your action) (auto) (auto)
↓
(buyer pays + delivered)
States:
updateFulfillment('shipped')).updateFulfillment('delivered')).Webhooks:
Core sends webhooks to your endpointUrl at each state change:
// Your endpoint receives:
POST https://mystore.com/webhook
{
transactionId: "uuid",
status: "shipped",
timestamp: "2026-03-10T10:00:00Z",
offer: { ... }
}
Handle webhooks to trigger fulfillment workflows, accounting updates, etc.
const beacon = createBeacon(config)
| Config | Type | Description |
|---|---|---|
externalId |
string | Your internal beacon identifier |
name |
string | Display name for your beacon |
description |
string | What the beacon does/sells |
endpointUrl |
string | HTTPS URL to receive webhooks |
capabilities |
string[] | Your capabilities: retail, shipping, drop-shipping, etc. |
coreUrl |
string | AURA Core URL (default: production) |
pollIntervalMs |
number | Session polling interval in milliseconds (default: 5000) |
| Method | Signature | Description |
|---|---|---|
register() |
async () => { beaconId } |
Register beacon with Core, returns beaconId |
startPolling() |
async () => void |
Start polling for new sessions |
stopPolling() |
async () => void |
Stop polling |
submitOffer() |
async (sessionId, offer) => void |
Submit offer to a session |
interpretIntent() |
async (intentText, catalog, options?) => InterpretResult |
Interpret buyer intent against product catalog using NLP |
beforeOffer() |
(validator) => beacon |
Register pre-offer validator (chainable) |
registerPolicies() |
(rules) => beacon |
Register business policies (chainable) |
onSession() |
(handler) => beacon |
Register session handler (chainable) |
onOfferAccepted() |
(handler) => beacon |
Register offer-accepted handler (chainable) |
onTransactionUpdate() |
(handler) => beacon |
Register transaction-update handler (chainable) |
updateFulfillment() |
async (transactionId, update) => void |
Update fulfillment status |
getTransaction() |
async (transactionId) => transaction |
Get transaction details |
| Hook | Signature | Description |
|---|---|---|
onSession |
(session, beacon) => Promise<void> |
Called for each new session during polling |
beforeOffer |
(session, offer) => Promise<offer \| undefined> |
Validator runs before submitOffer, can modify or block |
onOfferAccepted |
(transactionData) => Promise<void> |
Called when offer is committed |
onTransactionUpdate |
(event) => Promise<void> |
Called for any transaction status change |
Session:
{
sessionId: string,
status: 'market_forming' | 'committed' | 'cancelled' | 'expired',
intent: {
raw: string, // Original buyer text
parsed: {
keywords: string[] // Extracted keywords
}
},
constraints: {
categories: string[] // Buyer constraints are redacted — only categories visible
},
createdAt: string // ISO timestamp
}
Offer:
{
product: string, // Your product name/identifier
unitPrice: number, // Price per unit
quantity: number, // Quantity available
totalPrice?: number, // Optional: computed automatically if omitted
currency?: string, // Default: 'USD'
deliveryDate: string, // Date you can deliver (ISO format)
terms?: string, // Optional terms/conditions
metadata?: object // Optional custom data
}
Transaction:
{
transactionId: string,
sessionId: string,
status: 'committed' | 'shipped' | 'delivered' | 'fulfilled' | 'completed',
offer: Offer,
fulfillment?: {
status: 'shipped' | 'delivered',
reference?: string, // Tracking number
metadata?: object
},
createdAt: string,
updatedAt: string
}
beacon.interpretIntent() to match intents against your catalog instead of manual string matchingresult.confidence and result.suggestions to gauge intent completenessresult.matches is empty — don’t submit offers you can’t fulfillintent.raw for logging and debuggingbeforeOffer to enforce business rules at SDK levelupdateFulfillment('shipped') as soon as item shipsupdateFulfillment('delivered') when confirmed deliveredpollIntervalMs based on session volumebeacon.onSession(async (session) => {
try {
// Evaluate and submit offer
if (canFulfill(session)) {
await beacon.submitOffer(sessionId, offer);
}
} catch (error) {
console.error('Error handling session:', error);
// Don't re-throw — let polling continue
}
});
The Beacon SDK tracks operational metrics via an internal activity logger. These events are available via beacon.getActivitySummary():
| Event | Emitted When | Metadata |
|---|---|---|
session.received |
New session polled | sessionId |
offer.submitted |
Offer sent to Core | sessionId, offerId |
offer.rejected |
beforeOffer validator blocked offer |
reason |
intent.interpreted |
interpretIntent() succeeds |
matchCount, confidence, categoriesDetected, catalogSize |
intent.interpretation_failed |
interpretIntent() provider error |
error |
Activity Summary:
const summary = beacon.getActivitySummary();
// {
// sessions: { received: 42, ... },
// offers: { submitted: 18, rejected: 3, ... },
// interpretations: { total: 42, matched: 35, unmatched: 7, failed: 0 }
// }
See Beacon Implementations for: