@noxy-network/node-sdk
v2.2.0
Published
Decision Layer SDK for AI agents. Encrypted actionable decisions over gRPC.
Maintainers
Readme
@noxy-network/node-sdk
SDK for AI agent backends integrating with Noxy human-in-the-loop guardrails: send encrypted, actionable prompts (tool proposals, approvals, next-step hints) to registered user devices over gRPC so users can take decisions before work proceeds.
Before you integrate: Create your app at noxy.network. When the app is created, you receive an app id and an app token (auth token). This Node SDK authenticates with the relay using the app token (authToken in the client config). The app id is used by client SDKs (browser, iOS, Android, Telegram bot), not as the bearer token here.
Overview
Use this SDK to:
- Route decisions to devices bound to a logical identity (
identity_idon the relay) — whichever form your app uses when users register devices, e.g. Web3 wallet (0x…), email, phone (E.164-style), user_id, or another stable string — plus structured JSON you define (tool proposals, parameters, summaries). - Receive delivery outcomes from the relay (
DELIVERED,QUEUED,NO_DEVICES, etc.) plus adecision_idwhen the relay accepts the route. - Wait for human-in-the-loop resolution — users take decisions on-device and decision outcomes return via polling; prompts can expire if unanswered. The usual path is
sendDecisionAndWaitForOutcome(route + poll in one step). UsegetDecisionOutcome/waitForDecisionOutcomealone for finer control. - Query quota for your agent application on the relay.
- Resolve identity devices so each device receives its own encrypted copy of the decision.
The wire API uses agent.proto (noxy.agent.AgentService): RouteDecision, GetDecisionOutcome, GetQuota, GetIdentityDevices.
Communication is gRPC over TLS with Bearer authentication. Payloads are encrypted end-to-end (Kyber + AES-256-GCM) per device before leaving your process; the relay sees ciphertext only.
Architecture
The encrypted path covers SDK → relay and relay → device: decision content is ciphertext on both hops; the relay forwards without decrypting.
Ciphertext only (E2E) Ciphertext only (E2E)
┌──────────────────┐ gRPC (TLS) ┌─────────────────┐ gRPC (TLS), WSS ┌──────────────────┐
│ AI agent / │ ◄─────────────────► │ Noxy relay │ ◄──────────────────► │ User device │
│ orchestrator │ RouteDecision │ human-in-the- │ │ (users take │
│ (this SDK) │ GetDecisionOutcome│ loop relay │ │ decisions) │
│ │ GetQuota │ forwards only │ │ decrypts │
│ │ GetIdentityDevices│ │ │ │
└──────────────────┘ └─────────────────┘ └──────────────────┘Requirements
- Node.js >= 22
- ESM (
"type": "module")
Installation
npm install @noxy-network/node-sdk
# or
pnpm add @noxy-network/node-sdkQuick start
Route encrypted prompts and wait until users take decisions or the prompt expires (one call):
import { initNoxyAgentClient, NoxyHumanDecisionOutcome } from '@noxy-network/node-sdk';
const client = await initNoxyAgentClient({
endpoint: 'https://relay.noxy.network',
authToken: 'your-api-token',
decisionTtlSeconds: 3600,
});
const identity =
'0xaabb...'; // or e.g. 'user_42', '+15555550100', '[email protected]' — match how devices are linked in your Noxy app
const resolution = await client.sendDecisionAndWaitForOutcome(identity, {
kind: 'propose_tool_call',
tool: 'transfer_funds',
args: { to: '0x000000000000000000000000000000000000dEaD', amountWei: '1' },
title: 'Transfer 1 wei to the burn address',
summary: 'The agent is requesting approval to send 1 wei to the burn address.',
});
if (resolution.outcome === NoxyHumanDecisionOutcome.APPROVED) {
// run the proposed action
} else {
// REJECTED, EXPIRED, or non-approved — do not run the action (optionally branch on outcome)
}resolution.outcome is always a NoxyHumanDecisionOutcome (normalized from the relay: string enums from gRPC are mapped to 0–3). APPROVED → continue; anything else → stop or fallback. Use isTerminalHumanOutcome(outcome) for “finalized vs still pending”; for one-off getDecisionOutcome polls, also read pending.
Configuration
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| endpoint | string | Yes | Relay gRPC endpoint (e.g. https://relay.noxy.network). https:// is stripped; TLS is used. |
| authToken | string | Yes | Bearer token for relay auth (Authorization header). |
| decisionTtlSeconds | number | Yes | TTL for routed decisions (seconds). |
SendDecisionAndWaitOptions
Optional third argument to sendDecisionAndWaitForOutcome
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| initialPollIntervalMs | number | No | Delay after the first poll before the next attempt (ms). Default 400. |
| maxPollIntervalMs | number | No | Maximum delay between polls (ms). Default 30000. |
| maxWaitMs | number | No | Total time budget for polling (ms). Default 900000 (15 minutes). Exceeded → WaitForDecisionOutcomeTimeoutError. |
| backoffMultiplier | number | No | Multiplier applied to the poll interval after each attempt. Default 1.6. |
| signal | AbortSignal | No | When aborted, polling stops with an AbortError. |
API
initNoxyAgentClient(config): Promise<NoxyAgentClient>
Async init (loads Kyber WASM for post-quantum encapsulation).
NoxyAgentClient
sendDecision(identityId, actionableDecision): Promise<NoxyDeliveryOutcome[]>
Routes an encrypted decision to every device registered for the identity_id (wallet, email, phone, user_id, etc.—must match relay/device registration).
- Returns per device: relay
status(DELIVERED,QUEUED,NO_DEVICES, …),request_id, anddecision_idwhen the relay can track human resolution (use withGetDecisionOutcome).
getDecisionOutcome({ decisionId, identityId })
Single poll for human-in-the-loop state (pending + outcome). identityId is the same logical identity string you passed to sendDecision.
sendDecisionAndWaitForOutcome(identityId, actionableDecision, options?)
Runs sendDecision, then waitForDecisionOutcome using the first delivery that has a non-empty decision_id. Polling uses the same identityId as routing. Optional options is SendDecisionAndWaitOptions (same as WaitForDecisionOutcomeOptions without decisionId or identityId — e.g. maxWaitMs, backoff, signal).
- Returns
NoxyGetDecisionOutcomeResponse(same aswaitForDecisionOutcome). ThrowsSendDecisionAndWaitNoDecisionIdErrorif nodecision_idwas returned.
waitForDecisionOutcome(options)
Polls with exponential backoff (configurable initialPollIntervalMs, maxPollIntervalMs, backoffMultiplier) until:
outcomeis APPROVED, REJECTED, or EXPIRED, orpendingbecomesfalse, ormaxWaitMsis exceeded → throwsWaitForDecisionOutcomeTimeoutError.
Options: decisionId, identityId, initialPollIntervalMs (default 400), maxPollIntervalMs (default 30000), maxWaitMs (default 900000), backoffMultiplier (default 1.6), optional AbortSignal.
getQuota(): Promise<NoxyGetQuotaResponse>
Quota usage for the application.
Lower-level helpers (also exported)
waitForDecisionOutcome(fetch, options)— pass your ownGetDecisionOutcomecaller if needed.parseHumanDecisionOutcome,isTerminalHumanOutcomeWaitForDecisionOutcomeTimeoutError
Types
NoxyIdentityId: Logical identity string (wallet, email, phone, applicationuser_id, … — same convention as relayidentity_id).NoxyDeliveryStatus:DELIVERED|QUEUED|NO_DEVICES|REJECTED|ERRORNoxyHumanDecisionOutcome:PENDING|APPROVED|REJECTED|EXPIREDNoxyQuotaStatus:QUOTA_ACTIVE|QUOTA_SUSPENDED|QUOTA_DELETED
Encryption (summary)
- Kyber768 encapsulation per device
pq_public_key. - HKDF-SHA256 → AES-256-GCM key; random 12-byte nonce.
- JSON payload encrypted; only
kyber_ct,nonce,ciphertextcross the relay.
Development
pnpm install
pnpm run build
pnpm run typecheck
pnpm run testLicense
MIT
