Edictum
TypeScript SDK

TypeScript SDK

Install and use the Edictum TypeScript SDK to enforce contracts on AI agent tool calls in Node.js applications.

AI Assistance

Right page if: you want to install Edictum in a TypeScript or Node.js project, see the package overview, or write your first contract in TypeScript. Wrong page if: you are using Python -- see https://docs.edictum.ai/docs/quickstart. For a specific TS adapter, see https://docs.edictum.ai/docs/typescript/adapters. Gotcha: `fromYaml()` is synchronous. Use `fromYamlAsync()` in ESM contexts if you hit module-loading issues. YAML parsing requires `js-yaml` as an optional peer dependency.

Agents running in Node.js need the same contract enforcement as Python agents. The TypeScript SDK (@edictum/core) is a full port of the Python library with identical contract semantics -- same YAML, same pipeline, same deterministic enforcement.

Getting Started

Install

pnpm add @edictum/core js-yaml

Requires Node 22+. The js-yaml peer dependency is needed to load YAML contract bundles.

Write a Contract

Save this as contracts.yaml:

apiVersion: edictum/v1
kind: ContractBundle
metadata:
  name: agent-safety
defaults:
  mode: enforce
contracts:
  - id: deny-sensitive-reads
    type: pre
    tool: readFile
    when:
      args.path:
        contains_any: [".env", ".secret", "credentials", ".pem", "id_rsa"]
    then:
      effect: deny
      message: "Sensitive file '{args.path}' denied."

  - id: block-destructive-commands
    type: pre
    tool: runCommand
    when:
      any:
        - args.command: { starts_with: "rm " }
        - args.command: { starts_with: "DROP " }
        - args.command: { contains: "mkfs" }
    then:
      effect: deny
      message: "Destructive command denied: {args.command}"

  - id: session-limits
    type: session
    limits:
      max_tool_calls: 50
      max_attempts: 120
    then:
      effect: deny
      message: "Session limit reached."

Run It

import { Edictum, EdictumDenied } from '@edictum/core'

const guard = Edictum.fromYaml('contracts.yaml')

const readFile = async (args: Record<string, unknown>) => {
  return `contents of ${args.path}`
}

// Allowed: normal file read
const result = await guard.run('readFile', { path: 'readme.txt' }, readFile)
console.log(`OK: ${result}`)

// DENIED: agent tries to read .env
try {
  await guard.run('readFile', { path: '.env' }, readFile)
} catch (e) {
  if (e instanceof EdictumDenied) {
    console.log(`DENIED: ${e.reason}`)
  }
}

Expected output:

OK: contents of readme.txt
DENIED: Sensitive file '.env' denied.

The .env file was never read. The contract was evaluated in JavaScript, outside the LLM. The agent cannot talk its way past this check.

Packages

The TypeScript SDK is a monorepo of focused packages:

PackageDescriptionInstall
@edictum/corePipeline, contracts, audit, session, YAML engine. Zero runtime deps.pnpm add @edictum/core
@edictum/vercel-aiVercel AI SDK adapterpnpm add @edictum/vercel-ai
@edictum/claude-sdkClaude Agent SDK adapterpnpm add @edictum/claude-sdk
@edictum/langchainLangChain.js adapterpnpm add @edictum/langchain
@edictum/openai-agentsOpenAI Agents SDK adapterpnpm add @edictum/openai-agents
@edictum/openclawOpenClaw adapterpnpm add @edictum/openclaw
@edictum/serverServer SDK -- HTTP client, SSE hot-reload, audit sinkpnpm add @edictum/server
@edictum/otelOpenTelemetry spans and metricspnpm add @edictum/otel

All packages ship dual ESM + CJS builds.

Programmatic Contracts

Define contracts in code when you need dynamic logic:

import { Edictum, Verdict } from '@edictum/core'
import type { Precondition } from '@edictum/core'

const noRm: Precondition = {
  tool: 'Bash',
  check: async (envelope) => {
    if (envelope.bashCommand?.includes('rm -rf')) {
      return Verdict.fail('Cannot run rm -rf')
    }
    return Verdict.pass()
  },
}

const guard = new Edictum({ contracts: [noRm] })

Postconditions must set contractType: 'post' to avoid misclassification:

import type { Postcondition } from '@edictum/core'

const checkPII: Postcondition = {
  tool: 'searchDatabase',
  contractType: 'post',
  check: async (envelope, output) => {
    const text = String(output)
    if (/\b\d{3}-\d{2}-\d{4}\b/.test(text)) {
      return Verdict.fail('SSN detected in output')
    }
    return Verdict.pass()
  },
}

Observe Mode

Test contracts in production without denying any tool calls:

const guard = Edictum.fromYaml('contracts.yaml', { mode: 'observe' })

Calls that would be denied are logged as CALL_WOULD_DENY audit events but allowed to proceed. Review the audit trail, tune contracts, then switch to enforce.

See observe mode for the full workflow.

Dry-Run Evaluation

Test contracts without executing anything:

const result = await guard.evaluate('readFile', { path: '.env' })
console.log(result.verdict)  // "deny"
console.log(result.denyReasons[0])  // "Sensitive file '.env' denied."

Batch evaluation:

const results = await guard.evaluateBatch([
  { tool: 'readFile', args: { path: '.env' } },
  { tool: 'readFile', args: { path: 'readme.txt' } },
  { tool: 'runCommand', args: { command: 'rm -rf /' } },
])
for (const r of results) {
  console.log(`${r.toolName}: ${r.verdict.toUpperCase()}`)
}
// readFile: DENY
// readFile: ALLOW
// runCommand: DENY

Key Differences from Python

AspectPythonTypeScript
YAML loadingEdictum.from_yaml()Edictum.fromYaml() (camelCase)
Async YAMLNot neededEdictum.fromYamlAsync() for ESM
YAML stringEdictum.from_yaml_string()Edictum.fromYamlString()
Denial exceptionEdictumDeniedEdictumDenied (same name)
Server connectionEdictum.from_server()createServerGuard() from @edictum/server
Adapter extraspip install edictum[langchain]pnpm add @edictum/langchain (separate package)

See Migration from Python for a full comparison.

Next Steps

Last updated on

On this page