Edictum
TypeScript SDK

Server SDK

Connect TypeScript agents to Edictum Console for centralized contract management, hot-reload via SSE, and server-backed audit.

AI Assistance

Right page if: you want to connect a TypeScript agent to Edictum Console for centralized contract management, live hot-reload, or server-backed audit. Wrong page if: you want local-only enforcement without a server -- see https://docs.edictum.ai/docs/typescript. For Python server connection, see https://docs.edictum.ai/docs/console/connecting-agents. Gotcha: `createServerGuard()` is async and returns `{ guard, client, close }`. You must call `close()` on shutdown to flush audit events and stop the SSE watcher. HTTPS is required for non-loopback hosts unless `allowInsecure: true`.

Local YAML files work for development, but production agents need centralized contract management. The @edictum/server package connects TypeScript agents to Edictum Console -- contracts update via SSE without restarting the agent, audit events stream to the server, and approval workflows are handled server-side.

Getting Started

Install

pnpm add @edictum/core @edictum/server js-yaml

Create a server-connected guard

import { createServerGuard } from '@edictum/server'

const { guard, client, close } = await createServerGuard({
  url: 'https://console.example.com',
  apiKey: 'ed_live_...',
  agentId: 'my-agent',
  bundleName: 'production-contracts',
  environment: 'production',
})

Use with any adapter

import { VercelAIAdapter } from '@edictum/vercel-ai'

const adapter = new VercelAIAdapter(guard)
const result = await generateText({
  model: openai('gpt-4o'),
  tools: { readFile, runCommand },
  prompt: 'Summarize the Q3 report',
  ...adapter.asCallbacks(),
})

Clean up on shutdown

// Flush pending audit events, stop SSE watcher, close connections
await close()

createServerGuard Options

OptionTypeDefaultDescription
urlstringrequiredBase URL of the Edictum Console server
apiKeystringrequiredAPI key for authentication
agentIdstringrequiredAgent identifier registered with the server
bundleNamestring | nullnullNamed bundle to fetch. If null, waits for server assignment
environmentstring"production"Environment name for contract filtering
tagsRecord<string, string>nullTags for server-side filtering
mode"enforce" | "observe"bundle defaultOverride the bundle's defaults.mode
autoWatchbooleantrueStart SSE watcher for hot-reload
verifySignaturesbooleanfalseVerify Ed25519 signatures on bundles
signingPublicKeystring | nullnullEd25519 public key (hex) for verification
allowInsecurebooleanfalseAllow plaintext HTTP to non-loopback hosts
timeoutnumber30_000HTTP client timeout in ms
maxRetriesnumber3HTTP max retries
assignmentTimeoutnumber30_000Timeout for waiting for server assignment in ms
onDenyFunctionnullCallback on tool denial
onAllowFunctionnullCallback on tool approval
onWatchErrorFunctionnullCallback for SSE watcher errors

Hot-Reload via SSE

When autoWatch is enabled (the default), the guard subscribes to server-sent events. When a contract bundle is updated in the console, the new contracts are applied atomically -- no restart needed.

const { guard, close } = await createServerGuard({
  url: 'https://console.example.com',
  apiKey: 'ed_live_...',
  agentId: 'my-agent',
  bundleName: 'production-contracts',
  onWatchError: (error) => {
    console.error(`SSE error [${error.type}]: ${error.message}`)
  },
})

The onWatchError callback receives errors of four types:

TypeMeaning
signature_rejectedBundle signature verification failed
parse_errorBundle YAML or base64 decoding failed
fetch_errorHTTP request to fetch a bundle failed
reload_errorContract compilation or state replacement failed

On any error, the guard retains its current contracts. The SSE watcher continues listening for the next update.

Server Assignment

If you omit bundleName, the guard starts empty and waits for the server to assign a bundle:

const { guard, close } = await createServerGuard({
  url: 'https://console.example.com',
  apiKey: 'ed_live_...',
  agentId: 'my-agent',
  // No bundleName -- server decides which contracts to assign
  assignmentTimeout: 60_000,
})

The guard polls for policyVersion until the SSE watcher delivers an assignment or assignmentTimeout expires. If the timeout is reached, createServerGuard throws EdictumConfigError.

bundleName is required when autoWatch is false. Without a named bundle and no SSE watcher, the guard has no contracts and createServerGuard throws immediately.

Ed25519 Bundle Verification

For tamper-proof contract delivery, enable signature verification:

const { guard, close } = await createServerGuard({
  url: 'https://console.example.com',
  apiKey: 'ed_live_...',
  agentId: 'my-agent',
  bundleName: 'production-contracts',
  verifySignatures: true,
  signingPublicKey: 'a1b2c3d4e5f6...',  // hex-encoded Ed25519 public key
})

Every bundle received -- both the initial fetch and subsequent SSE updates -- is verified against the public key. If verification fails, the bundle is rejected and the guard retains its current contracts. The onWatchError callback fires with type: 'signature_rejected'.

See bundle signing for key generation and server configuration.

Server Audit Sink

By default, createServerGuard configures a ServerAuditSink that streams audit events to the console. This is automatic -- you do not need to configure it separately.

To use a custom audit sink instead:

import { FileAuditSink } from '@edictum/core'

const { guard, close } = await createServerGuard({
  url: 'https://console.example.com',
  apiKey: 'ed_live_...',
  agentId: 'my-agent',
  bundleName: 'production-contracts',
  auditSink: new FileAuditSink('audit.jsonl'),
})

Low-Level Client

For advanced use cases, use EdictumServerClient directly:

import { EdictumServerClient } from '@edictum/server'

const client = new EdictumServerClient({
  baseUrl: 'https://console.example.com',
  apiKey: 'ed_live_...',
  agentId: 'my-agent',
  env: 'production',
})

// Fetch a bundle manually
const bundle = await client.get('/api/v1/bundles/my-bundle/current', {
  env: 'production',
})

TLS Enforcement

HTTPS is required when connecting to non-loopback hosts. This prevents API keys from being transmitted in plaintext. To connect to a local development server over HTTP:

// Only for local development -- never use in production
const { guard, close } = await createServerGuard({
  url: 'http://localhost:8000',
  apiKey: 'ed_dev_...',
  agentId: 'my-agent',
  bundleName: 'dev-contracts',
  allowInsecure: true,
})

Loopback addresses (localhost, 127.0.0.1, ::1) are allowed over HTTP without allowInsecure.

Next Steps

Last updated on

On this page