Quickstart: TypeScript
Wire @wytness/ai into your AGT-instrumented Node app in about five minutes. Install, set four env values (plus the optional endpoint), call init(). Every audit entry AGT-TS chains is auto-forwarded to Wytness with PII pseudonymisation and a per-event Ed25519 envelope signed with your customer-held key.
1. Install the wrapper
Microsoft's @microsoft/agent-governance-sdk@5.0.0 is a hard dependency of wytness-ai — installed automatically. Targets Node 18+ (uses the global fetch).
npm install @wytness/ainpm install @microsoft/agent-governance-sdk and point its native CloudEvents sink at api.wytness.ai/ingest. You get the dashboard but not customer-held PII keys or Evidence Packs.2. Generate keys
Wytness adds two customer-held primitives on top of AGT — per-event Ed25519 non-repudiation and zero-knowledge PII pseudonymisation. Both rely on keys you generate in your browser; the private halves never reach Wytness.
Open the Keys page. Click Generate on each of the three key cards (signing, PII encryption, PII HMAC), then download each private half. The public halves auto-register against your org. The four env values you need are then shown as a copy-block on the Onboarding "Instrument" step:
WYTNESS_API_KEY— transport credential (server-issued, prefixwyt_api_live_…)WYTNESS_SIGNING_KEY— base64 Ed25519 private key, 32 bytes decoded (always required — non-repudiation is the product promise)WYTNESS_PII_PUBKEY— base64 X25519 public key, 32 bytes (optional; required together with the secret to enable PII pseudonymisation)WYTNESS_PII_SECRET— base64 HMAC secret, ≥16 bytes (optional; required together with the pubkey)
3. Set the environment
Set the four values as environment variables. In production use your secrets manager (Azure Key Vault, AWS Secrets Manager, Vercel env, k8s secrets, etc.); in local dev a .env file loaded by Node’s built-in --env-file flag is fine. init() reads them directly — no constructor boilerplate. If you use a .env file locally, add it to .gitignore before the first git add.
WYTNESS_API_KEY=wyt_api_live_...WYTNESS_ENDPOINT=https://api.wytness.aiWYTNESS_SIGNING_KEY=<base64 Ed25519 private key>WYTNESS_PII_PUBKEY=<base64 X25519 public key>WYTNESS_PII_SECRET=<base64 HMAC secret>4. Wire it in
Call init() once at process start, before AGT-TS's AuditLogger is first instantiated. The wrapper hooks AGT's audit logger cleanly so every entry it chains flows through the Wytness path — your existing AGT code stays unchanged.
import { init, shutdown, AuditLogger } from "@wytness/ai"; // Reads WYTNESS_* env vars by default. piiFields lists dotted paths// whose values should be pseudonymised (emails, customer names, etc.).await init({ piiFields: ["customer.email", "customer.name"] }); // Use AGT-TS exactly as you would without the wrapper.const audit = new AuditLogger(); audit.log({ agentId: "billing-agent", action: "tool_invocation:send_invoice", decision: "allow",}); // Drain the in-flight batch on exit.await shutdown();init({ autoWire: false }) if you'd rather attach the audit sink yourself — see the SDK API reference.AuditLogger.log() takes a small camelCase entry shape. Extra Wytness fields (data, outcome, resource) can be stashed on the next entry with setNextEntryExtras(...). See the SDK API reference.5. Run it
Run your agent normally. The wrapper batches events (default 100 or 5 seconds, whichever first), pseudonymises declared PII fields, signs each event with your Ed25519 key, and POSTs the canonical-shape envelope to api.wytness.ai/ingest. Failed POSTs are written to a local dead-letter file for replay once connectivity returns.
npx tsx agent.tsOpen your app.wytness.ai dashboard within ~5 seconds of the first event to confirm it arrived. Events show the AGT identity, the AGT-supplied hash chain, and the Wytness envelope signature side-by-side.
What you'll see in the dashboard
- Each AGT-TS
AuditEntryas a row in the Events page - Three-layer verdict: AGT identity (Ed25519 DID), AGT-supplied hash chain, Wytness envelope (Ed25519)
- PII fields rendered as typed tokens (
EMAIL_3c866e6f, etc.); reveal in-browser with your X25519 private key - Source format chip showing
wytness_agt_ts(TypeScript wrapper) on every event - Per-agent Identity Badge pulled from AGT-TS's DID + trust score
Full working example
Save as test-audit.ts, then run with npx tsx test-audit.ts.
import { init, shutdown, setNextEntryExtras, AuditLogger } from "@wytness/ai"; await init({ piiFields: ["customer.email"] }); const audit = new AuditLogger(); setNextEntryExtras({ data: { customer: { email: "alice@example.com" }, message: "Hi" }, outcome: "success",});audit.log({ agentId: "test-agent", action: "tool_invocation:hello_world", decision: "allow",}); setNextEntryExtras({ data: { a: 40, b: 2, result: 42 }, outcome: "success",});audit.log({ agentId: "test-agent", action: "tool_invocation:add_numbers", decision: "allow",}); await shutdown();console.log("Done — check your dashboard.");What happens under the hood
- AGT-TS runs unchanged — identity check, policy evaluation, hash-chain entry
- The wrapper's
WytnessAuditSinkreceives the entry, transforms camelCase to canonical snake_case, applies yourpiiFieldspseudonymisation - The canonical-shape
datapayload is signed with your Ed25519 key, wrapped in a CloudEvents 1.0 envelope - The batch is POSTed to
api.wytness.ai/ingestasapplication/vnd.wytness.agt+json - Failed POSTs are written to a local dead-letter file for replay once connectivity returns
Automatic LLM capture
Prefer not to write AuditLogger().log(...) by hand? Wrap your model client after init() and every Anthropic Messages API call (including the beta namespace) and every OpenAI Chat Completions and Responses call — streaming included — is recorded automatically — prompt, reply, reasoning (Anthropic extended thinking), latency, and token usage. Token usage needs no opt-in: on OpenAI streams the SDK requests it and hides the resulting usage-only chunk, so your loop is unchanged. Other providers are not captured. Captured calls ride the same signed, PII-pseudonymised path as manual entries.
import Anthropic from "@anthropic-ai/sdk";import { init, shutdown, wrapAnthropic } from "@wytness/ai"; await init({ agentDid: "did:web:acme.example:support-bot", piiFields: ["customer.email"] }); const client = wrapAnthropic(new Anthropic());await client.messages.create({ // recorded automatically — no audit.log() needed model: "claude-haiku-4-5", max_tokens: 512, messages: [{ role: "user", content: "Summarise ticket #4821" }],}); await shutdown();wrapOpenAI(new OpenAI()). Streaming calls (stream: true and messages.stream(...)) are captured too — one event per call when the stream ends; helper streams are captured on completion via the SDK's own events, even if your code never reads them. OpenAI streamed usage needs no opt-in — the SDK requests it and hides the resulting usage-only chunk, so your loop is unchanged.Using a TypeScript agent framework?
AGT-TS 5.0.0 ships GenericFrameworkAdapter only — no framework-specific adapters for LangChain.js / AutoGen / OpenAI Agents / Semantic Kernel today. Implement one adapter method against your framework's tool-call lifecycle, and every entry AGT-TS chains flows into Wytness via wytness-ai. See the framework integrations guide for the adapter pattern.
Next steps
- PII Protection — declare custom pseudonymisation fields, generate keys, reveal tokens in the dashboard
- SDK API reference — full
init()/shutdown()options, manual wiring, batch tuning - Event schema — canonical-shape
datapayload + Wytness envelope layout - Quickstart (Python) — same shape for
wytness-ai(PyPI)
Ready to audit your AI agents?
Start recording