@wytness/ai
v1.0.0
Published
Wytness — the blackbox recorder for AI agents. Customer-held PII keys, per-event Ed25519 envelope, audit-ready evidence packs. Built on Microsoft's Agent Governance Toolkit.
Downloads
123
Maintainers
Readme
wytness
Wytness wrapper for Microsoft's Agent Governance Toolkit (AGT). Adds the two security properties AGT does not ship:
- Non-repudiation — per-event Ed25519 signature with a customer-held private key. Asymmetric verification: anyone with the public key can verify, no shared secret.
- Zero-knowledge PII — HMAC-SHA256 pseudonyms + X25519 + ChaCha20-Poly1305 token-map encryption. Wytness never sees raw PII.
These compose with AGT's own primitives (Ed25519 agent identity, SHA-256 hash chain). See docs/agt-wrapper-design.md §2.5 for the full layered model.
Mirrors wytness-ai (Python) — same wire format, same init() / shutdown() lifecycle.
Status
v1.0.0 — first stable release. Coordinated launch with wytness-ai (Python, on PyPI). wytness monkey-patches Microsoft's @microsoft/[email protected] AuditLogger.prototype.log on init({ autoWire: true }) (default). Every audit entry AGT chains is automatically forwarded into the Wytness envelope path with full 3-layer evidence (AGT identity + AGT chain + Wytness envelope, envelope_version: 2 — signature covers data + envelope id/time/type).
The TS wrapper transforms AGT-TS's camelCase 6-field stub into the canonical snake_case shape per docs/agt-wrapper-design.md §13. The Wytness data payload is byte-comparable to the Python wrapper for the same canonical inputs — cross-language dashboards / Evidence Packs / verify routes see uniform inputs.
Tests: 107 vitest green covering config validation (XOR PII pairing guard, env-var fallback), envelope signing + tamper-detection (v2 sig scope), PII pseudonymisation + auto-attach, transport batching + retry + dead-letter file + jittered backoff, init/shutdown idempotency, AGT-TS wrap end-to-end, wire-format byte parity with the Python SDK fixture, and stats/health pre-init safety. Backend dual-accepts envelope_version 1 + 2 during the deprecation window — see CHANGELOG.md.
Install
npm install @wytness/ai@microsoft/[email protected] is a hard dependency — installed automatically. Targets Node 18+ (uses the global fetch).
Hello world
import { init, shutdown, setNextEntryExtras, AuditLogger } from "@wytness/ai";
await init({
apiKey: process.env.WYTNESS_API_KEY,
piiPubkey: process.env.WYTNESS_PII_PUBKEY, // browser-generated X25519
piiSecret: process.env.WYTNESS_PII_SECRET, // browser-generated HMAC
signingKey: process.env.WYTNESS_SIGNING_KEY, // browser-generated Ed25519
piiFields: ["customer.email"],
});
// AGT-TS owns identity + chain. The wrapper has already monkey-patched
// AuditLogger.prototype.log — every entry below also flows to Wytness.
const logger = new AuditLogger();
// Stash the Wytness extras AGT-TS doesn't accept directly (data, outcome, …).
setNextEntryExtras({
data: { customer: { email: "[email protected]" }, query: "best espresso" },
outcome: "success",
resource: "https://example.com",
});
logger.log({
agentId: "audit-test-agent",
action: "tool_invocation:search_web", // colon-split → event_type + action
decision: "allow", // → policy_decision
});
await shutdown();PII is pseudonymised before egress, the canonical-JSON envelope is signed with your Ed25519 key, and the batch lands at api.wytness.ai/ingest. Failed POSTs land in ~/.wytness/wytness-deadletter.jsonl.
AuditLogger is re-exported from @wytness/ai, so one import line covers both lifecycle and audit. The original import { AuditLogger } from "@microsoft/agent-governance-sdk" is identical and still works.
Browser-generated keys come from the Wytness dashboard's Keys page (Signing Key, PII Encryption Key, and PII HMAC Key cards); the dashboard never sees your private halves.
Feature parity with wytness-ai (Python)
Both SDKs ship the same wire format (snake_case canonical shape, identical envelope structure, byte-comparable for the same logical input — verified by tests/integration/wireFormat.parity.test.ts). Behavioural surface differences:
| Surface | Python | TypeScript | Notes |
|---|---|---|---|
| AGT AuditLog auto-wire | ✓ — patches agentmesh.governance.audit.AuditLog | ✓ — patches AuditLogger.prototype.log from @microsoft/agent-governance-sdk | Both default auto_wire=True / autoWire: true. |
| AGT GovernanceEvent auto-wire | ✓ — registers WytnessEventSink on GovernanceEventProcessor | ✗ — manual (getEventSink().emit(...)) | AGT-TS 3.7.0 has no GovernanceEventProcessor registry. Tracked as V2-AGT-TS-EVENT-SINK; will wire automatically when Microsoft ships the registry. |
| Wire-format byte parity | ✓ | ✓ | Cross-language fixture checked in. |
| Ed25519 envelope signing | ✓ | ✓ | Identical canonical-JSON encoding + sig scope. |
| PII pseudonymisation | ✓ | ✓ | Identical HMAC + X25519 + ChaCha20-Poly1305 primitives. |
| Framework adapter compatibility tests | ✓ — 12 integrations × 3 axes | ✗ — AGT-TS ships zero framework adapters in 3.7.0 | |
| stats() + health() accessors | ✓ — wytness.stats() / health() | ✓ — wytnessAgt.stats() / health() | Same shape; snake_case (Py) vs camelCase (TS). |
Manual wiring (no monkey-patch)
If you'd rather wire explicitly, pass autoWire: false and call the audit sink directly. The sink accepts both AGT-TS camelCase and pre-canonical snake_case shapes — the canonical-shape transform runs automatically when it sees camelCase keys.
import { init, getAuditSink } from "@wytness/ai";
await init({ /* … */, autoWire: false });
const sink = getAuditSink()!;
sink.write({
agentId: "agent-1",
action: "tool_invocation:search_web",
decision: "allow",
hash: "deadbeef…",
previousHash: "0".repeat(64),
data: { query: "…" },
});Wire format
Each POST batch is a JSON array of CloudEvents envelopes:
[
{
"specversion": "1.0",
"id": "<uuid>",
"source": "wytness",
"type": "AuditEntry",
"datacontenttype": "application/json",
"time": "2026-05-22T00:00:00.000Z",
"data": {
"timestamp": "2026-05-22T00:00:00.000Z",
"agent_id": "audit-test-agent",
"event_type": "tool_invocation",
"action": "search_web",
"policy_decision": "allow",
"entry_hash": "<AGT-supplied SHA-256 hex>",
"previous_hash": "",
"entry_id": "<uuid>",
"data": { "query": "…", "customer": { "email": "EMAIL_<token>" } },
"outcome": "success",
"resource": "https://example.com"
},
"wytness_envelope": {
"envelope_version": 1,
"key_id": "<16 chars base64url SHA-256(pubkey)>",
"signature_ed25519": "<base64 Ed25519 sig over canonicalJson(data)>",
"pseudonymization_version": "1"
}
}
]The data field is the canonical snake_case shape — byte-comparable to what wytness-ai (Python) emits for the same logical input. CH agt_did, agt_merkle_entry_hash, agt_merkle_previous_hash, policy_decision columns map 1:1.
Content-Type: application/vnd.wytness.agt+json — every envelope is signed (Wytness has no unsigned mode; signing is the product's value prop).
License
MIT. Built on @microsoft/agent-governance-sdk (MIT, Microsoft).
