The TypeScript SDK for verifiable agent work.
Produce a portable, signed account of what an agent did, and verify one
offline, without trusting the system that produced it.
import { Verifier } from "verified-typescript";
import { readPackage, readTrustRegistry } from "verified-typescript/node";
const examples = "examples/standalone-verification";
const verifier = new Verifier({ trust: readTrustRegistry(`${examples}/trust.json`) });
const result = verifier.verify(readPackage(`${examples}/contradicted.json`));
console.log(result.verificationStatus, result.decision); // complete CONTRADICTED
console.log(result.sourceTruthfulness.status); // not_established
result.requireSupported();
// RequirementNotMet: the reproduced decision is CONTRADICTED, not SUPPORTEDStatus: developer preview (0.1.0). The Work Verification Protocol (WVP) is a draft in every version. This SDK has not been security-audited, and its API will change. Read what is and is not supported before relying on it.
Open source under the Apache License 2.0. On npm as
verified-typescript.
What did this agent do, under which authority, with what evidence, according to which contract, and can an independent party reproduce the result?
verified-typescript lets a TypeScript or JavaScript agent system produce a
portable, signed account of a unit of work, and lets anyone verify it offline.
Verification is performed by the same Rust crates as the
verified CLI and the
Python SDK, compiled to
WebAssembly; the TypeScript layer adds an API, not a second implementation.
npm install verified-typescriptNode.js 22 or later. The WebAssembly module ships inside the package: there
is nothing to compile and no install script. To work on the SDK, from a
checkout with Node.js 22.18 or later, a Rust toolchain (1.99+, with the
wasm32-unknown-unknown target) and wasm-bindgen-cli 0.2.129:
git clone --recurse-submodules https://github.com/dekrops/verified-typescript
cd verified-typescript
npm ci
npm run build:native # compiles the Rust crates to WebAssembly
npm test # 300+ tests, including the WVP conformance corpusThe package has no runtime dependencies. It is ES modules only, and has three entry points:
| Import | Holds | Runs on |
|---|---|---|
verified-typescript |
Verification, trust, contracts, evidence, the work lifecycle | Anything with WebAssembly |
verified-typescript/instrumentation |
WorkSession, verifiedTool, traces |
Runtimes with node:async_hooks |
verified-typescript/node |
File helpers | Runtimes with node:fs |
Tested on Node.js 22 and 24. In browsers, run it in a module Web Worker: the
module is compiled there synchronously, without blocking the page. It has been
verified in Chrome in a Vite-built worker. A page's Content Security Policy must
allow WebAssembly compilation, so add 'wasm-unsafe-eval' to script-src; no
broader permission is needed. Deno and Bun are expected to work, and are not
tested. Runtimes that forbid compiling WebAssembly from bytes at run time,
Cloudflare Workers among them, cannot load the module as it is packaged.
You hold a package file and a trust registry you chose. No account, no network, no agent framework.
import { Verifier } from "verified-typescript";
import { readContract, readPackage, readTrustRegistry } from "verified-typescript/node";
const examples = "examples/standalone-verification";
const verifier = new Verifier({
trust: readTrustRegistry(`${examples}/trust.json`), // yours, never the package's
expect: {
// what you agreed to
tenant: "acme-synthetic",
contract: readContract("examples/contracts/invoice-posting.json"),
},
});
const result = verifier.verify(readPackage(`${examples}/contradicted.json`));
console.log(result.verificationStatus); // complete <- the verification succeeded
console.log(result.decision); // CONTRADICTED <- the work did not
console.log(result.explain());Those are two different facts, and the API never merges them:
| You want to know | Read |
|---|---|
| Is the file well formed and unaltered? | result.integrity |
| Is the evidence authentic under my registry? | result.evidenceSignatures, result.signerAuthority |
| Did every protocol check run and pass? | result.verificationStatus |
| What does the contract yield from the evidence? | result.decision (null unless reproduced) |
| Is that the outcome I need? | result.requireSupported() |
| Did the sources tell the truth? | Nothing can tell you. result.sourceTruthfulness is always not_established. |
There is no valid flag. A result is an object, so if (result) is always
true and tests nothing. To enforce an outcome, say which one:
import { RequirementNotMet, Verifier } from "verified-typescript";
import { readPackage, readTrustRegistry } from "verified-typescript/node";
const examples = "examples/standalone-verification";
const verifier = new Verifier({ trust: readTrustRegistry(`${examples}/trust.json`) });
verifier.verify(readPackage(`${examples}/supported.json`)).requireSupported();
try {
verifier.verify(readPackage(`${examples}/undetermined.json`)).requireSupported();
} catch (error) {
if (!(error instanceof RequirementNotMet)) throw error;
console.log(error.message); // the reproduced decision is UNDETERMINED, not SUPPORTED
}Without a registry (new Verifier({ trust: null }), which you must write
out), the result is incomplete: integrity only, no decision. The decision a
package carries is its issuer's claim until you reproduce it.
Run the agent inside a WorkSession and wrap its tools. No framework or
tracing backend is imposed; synchronous and asynchronous functions both work.
import { verifiedTool, WorkSession } from "verified-typescript/instrumentation";
import { readContract } from "verified-typescript/node";
const contract = readContract("examples/contracts/invoice-posting.json");
const postInvoice = verifiedTool(
{
workType: "invoice.post",
contract,
inputs: (invoiceId: string) => ({ invoice: invoiceId }),
},
(invoiceId: string) => "accepted", // what the ERP's API answered
);
const session = new WorkSession({ agent: "invoice-agent", tenant: "acme-synthetic" });
session.run(() => {
session.turn(() => postInvoice("INV-123"));
});
const [work] = session.works;
console.log(work?.state); // open
console.log(session.events.map((event) => event.kind));The tool returned, and nothing is established. The wrapper recorded that
the call happened and opened a work item under the contract; it did not record
success. The work stays open until evidence from an identified source is
attached:
open ──attach(evidence)──▶ open ──claim()──▶ claimed ──issue({ trust })──▶ issued
│
decision: SUPPORTED | CONTRADICTED | UNDETERMINED
issue() evaluates the contract and seals a package. With only the agent's
word, the decision is UNDETERMINED. The full flow, with a simulated ERP as a
separate party, is examples/invoice-posting.
There is no framework adapter yet. See agent runtime integration.
Evidence is signed where the observation is made, by a key whose authority is declared explicitly: one issuer, one tenant, named source systems.
import {
Ed25519Signer,
EvidenceSource,
SigningAuthority,
SigningAuthorityError,
} from "verified-typescript";
// Use a KMS in production.
const signer = new Ed25519Signer({ keyId: "erp-gateway-1", seed: new Uint8Array(32).fill(7) });
const authority = new SigningAuthority({
keyId: "erp-gateway-1",
issuer: "00000000-0000-4000-8000-0000000000e1",
tenant: "acme-synthetic",
sources: ["erp"], // this key speaks for the ERP, and nothing else
});
const gateway = new EvidenceSource({ system: "erp", authority, signer });
const posting = gateway.record(
"posting",
{ document: "DOC-1", invoice: "INV-123", amount: 125_000 }, // EUR 1250.00 at scale 2
{ subject: { system: "erp", kind: "invoice", key: "INV-123" } },
);
console.log(posting.source, posting.keyId);
try {
new EvidenceSource({ system: "payments", authority, signer });
} catch (error) {
if (!(error instanceof SigningAuthorityError)) throw error;
console.log(error.message); // not declared to speak for source "payments"
}The local check prevents mistakes. The security boundary is elsewhere: a
relying party's registry says which key may speak for which system, and its
verifier refuses a statement signed by any other key (SOURCE_NOT_AUTHORIZED).
An agent's key is authorized for the agent's runtime, so an agent cannot turn
"I posted the invoice" into "the ERP says it was posted".
A signature establishes who said something and that it was not altered. It does not establish that it is true. See the trust model.
| Thing | What it is | Who stands behind it | In this SDK |
|---|---|---|---|
| Execution telemetry | What a runtime logged about itself | Nobody; it is unsigned | ExecutionTrace |
| Agent claim | An assertion that work was done, against a contract | The claimant | Claim |
| Signed evidence statement | What an identified source says it observed | The signer, for the systems a registry authorizes | SignedEvidence |
| Verified decision | What the contract yields from authenticated evidence, reproduced | Whoever re-runs it: you | VerificationResult.decision |
| Package ("portable Work Record") | Claim, contract, evidence and decision in one sealed file | Its issuer, if it signed the decision | WorkPackage |
| Work record | The record of one verification of a package | The verifier that computed it | WorkRecord |
An attestation (a party signing that a verdict holds) appears in the protocol only as the issuer's signature over a decision. Multi-claim attestation bundles, as in Agent Compliance Center, are application formats and are not part of this SDK.
The protocol's encoding has whole numbers only. A number must be a safe
integer and a bigint is written as an integer of any size; a fraction is
refused, never rounded. Write amounts as whole units of the contract's scale
(EUR 12.50 at scale 2 is 1250).
All run offline with synthetic data, from a checkout after npm run build:
node examples/<dir>/<script>.ts.
| Example | Shows |
|---|---|
standalone-verification |
A third party verifying three packages; all verify, one is supported |
invoice-posting |
A custom agent loop, a tool, and an ERP gateway as a separate evidence source |
chatbot-compliance |
Turn and session claims under separate contracts; the session is not the sum of its turns |
enterprise-evidence |
Scoped signing authority, a pluggable signer, and three relying parties |
The three packages in standalone-verification are byte for byte the ones the
Python SDK produces from the same inputs.
Working, with tests: offline verification of wvp-0.1 and wvp-0.2 packages;
relying-party expectations and work records; trust registries; evidence
statements, chains and admission; Work Contract Language 1; work-unit identity
and nullifiers; issuing wvp-0.2 packages; framework-independent
instrumentation.
Not here: framework adapters; an asynchronous signer
interface (Signer.sign is synchronous); a client for the hosted Verified
platform (it has no stable service API to call yet); claim registration v1;
statement chaining when producing evidence. Details and reasons are in the
roadmap and the
compatibility assessment.
This SDK contains no billing, settlement or compliance rules, and does not decide whether verified work is accepted, payable or compliant.
- Architecture overview
- Ecosystem boundaries
- Rust and TypeScript interop
- Agent runtime integration
- Protocol compatibility
- Trust model
- Roadmap
- Decisions
dekrops/wvp: the specifications and conformance vectors. Normative.dekrops/verified-cli: the Rust implementation this SDK binds, and theverifiedcommand.dekrops/verified-python: the Python SDK, with the same result model and names.dekrops/agent-billing-center,dekrops/agent-compliance-center: reference applications built on verified work.dekrops/verified: the commercial Work Verification Platform. Not required by anything here.
Apache-2.0. See LICENSE and NOTICE. The conformance
corpus under conformance/wvp is a submodule with its own licenses.