noxy-sdk 2.0.1

Decision Layer SDK for AI agents. Encrypted actionable decisions over gRPC.
Documentation

πŸ“¦ @noxy-network/rust-sdk

SDK for AI agent runtimes integrating with the Noxy Decision Layer: send encrypted, actionable decision payloads (tool proposals, approvals, next-step hints) to registered agent devices over gRPC.

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 Rust SDK authenticates with the relay using the app token (auth_token in NoxyConfig). 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 Web3 identity (0x… address) β€” structured JSON you define (e.g. proposed tool calls, parameters, user-visible summaries).
  • Receive delivery outcomes from the relay (DELIVERED, QUEUED, NO_DEVICES, etc.) plus a decision_id when the relay accepts the route.
  • Wait for human-in-the-loop resolution β€” the wallet user approves, rejects, or the decision expires. The usual path is send_decision_and_wait_for_outcome (route + poll in one step). Use get_decision_outcome / wait_for_decision_outcome alone 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)       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  AI agent /      β”‚ ◄─────────────────► β”‚  Noxy relay     β”‚ ◄──────────────────► β”‚  Agent device    β”‚
β”‚  orchestrator    β”‚   RouteDecision     β”‚  (Decision      β”‚                      β”‚  (human approves β”‚
β”‚  (this SDK)      β”‚   GetDecisionOutcomeβ”‚   Layer)        β”‚                      β”‚   or rejects)    β”‚
β”‚                  β”‚   GetQuota          β”‚   forwards only β”‚                      β”‚   decrypts       β”‚
β”‚                  β”‚   GetIdentityDevicesβ”‚                 β”‚                      β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Requirements

  • Rust >= 1.70
  • Tokio async runtime

Installation

[dependencies]
noxy-sdk = "2.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde_json = "1"

Quick start

Route a decision and wait until the user approves, rejects, or the decision expires (one call):

use noxy_sdk::{init_noxy_agent_client, NoxyConfig, NoxyHumanDecisionOutcome};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = init_noxy_agent_client(NoxyConfig {
        endpoint: "https://relay.noxy.network".into(),
        auth_token: "your-api-token".into(),
        decision_ttl_seconds: 3600,
    })
    .await?;

    let identity = "0x...".to_string();
    let resolution = client
        .send_decision_and_wait_for_outcome(
            identity,
            &serde_json::json!({
                "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.",
            }),
            None,
        )
        .await?;

    if resolution.outcome == NoxyHumanDecisionOutcome::Approved {
        // run the proposed action
    }
    Ok(())
}

resolution.outcome is a NoxyHumanDecisionOutcome. Approved β†’ continue; anything else β†’ stop or fallback. Use is_terminal_human_outcome(outcome) for β€œfinalized vs still pending”; for one-off get_decision_outcome 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.
auth_token String Yes Bearer token for relay auth (Authorization header).
decision_ttl_seconds u32 Yes TTL for routed decisions (seconds).

SendDecisionAndWaitOptions

Optional third argument to send_decision_and_wait_for_outcome.

Option Type Required Description
initial_poll_interval_ms Option<u64> No Delay after the first poll before the next attempt (ms). Default 400.
max_poll_interval_ms Option<u64> No Maximum delay between polls (ms). Default 30000.
max_wait_ms Option<u64> No Total time budget for polling (ms). Default 900000 (15 minutes). Exceeded β†’ WaitForDecisionOutcomeTimeoutError.
backoff_multiplier Option<f64> No Multiplier applied to the poll interval after each attempt. Default 1.6.

API

init_noxy_agent_client(config) -> Result<NoxyAgentClient, Error>

Async init (establishes gRPC + Kyber for post-quantum encapsulation).

NoxyAgentClient

send_decision(identity_address, actionable_decision)

Routes an encrypted decision to every device registered for the identity.

  • Returns per device: relay status, request_id, and decision_id when applicable.

get_decision_outcome(decision_id, identity_id)

Single poll for human-in-the-loop state (pending + outcome).

send_decision_and_wait_for_outcome(identity_address, actionable_decision, options?)

Runs send_decision, then wait_for_decision_outcome using the first delivery with a non-empty decision_id. Polling uses identity_address as identity_id.

  • Returns NoxyGetDecisionOutcomeResponse. Errors with SendDecisionAndWaitNoDecisionIdError if no decision_id was returned (boxed Error).

wait_for_decision_outcome(options)

Polls with exponential backoff until terminal outcome, pending == false, or max_wait_ms β†’ WaitForDecisionOutcomeTimeoutError.

get_quota()

Quota usage for the application.

Helpers (re-exported)

  • is_terminal_human_outcome
  • WaitForDecisionOutcomeTimeoutError, SendDecisionAndWaitNoDecisionIdError

Types

  • NoxyDeliveryStatus: Delivered | Queued | NoDevices | Rejected | Error
  • NoxyHumanDecisionOutcome: Pending | Approved | Rejected | Expired
  • NoxyQuotaStatus: QuotaActive | QuotaSuspended | QuotaDeleted

Encryption (summary)

  1. Kyber768 encapsulation per device pq_public_key.
  2. HKDF-SHA256 β†’ AES-256-GCM key; random 12-byte nonce.
  3. JSON payload encrypted; only kyber_ct, nonce, ciphertext cross the relay.

License

MIT