Popular starting points. Use Tab or arrow keys to browse results.

Skip to content
Aten SecurityThoth docs

TypeScript SDK

Full reference for the Thoth TypeScript SDK — ThothConfig, instrument(), wrapAnthropicTools(), wrapOpenAITools(), ThothClient, EnforcementMode, ThothPolicyViolation.

Reference examples

Start with Your first governed agent for a complete runnable example and tested package version. Examples here focus on individual APIs and may depend on callbacks or clients defined by your application. Replace illustrative tenant and endpoint values with the supplied connection details; agree on server policy before using real tools.

Installation

npm install @atensec/thoth
# or
pnpm add @atensec/thoth
# or
yarn add @atensec/thoth

Node.js 22+ required. The package ships full TypeScript declarations and requires a runtime with fetch + AbortSignal.timeout support.


Preferred module APIs:

  • instrument()
  • wrapAnthropicTools()
  • wrapOpenAITools()

Legacy compatibility:

  • ThothClient remains supported for backward compatibility.
  • ThothClient.wrap() is a legacy alias to ThothClient.instrument().
import { ThothClient } from "@atensec/thoth";
 
const client = new ThothClient({
  tenantId: "acme-corp",
  userId: "alice@acme.com",
});
 
client.instrument(agent, {
  agentId: "invoice-processor-v2",
  approvedScope: ["search_docs"],
  apiUrl: "https://enforce.acme-corp.atensecurity.com",
});

ThothConfig

interface ThothConfig {
  agentId: string;
  approvedScope: string[];
  tenantId: string;
  userId?: string; // default: "system"
  enforcement?: EnforcementMode; // default: EnforcementMode.BLOCK
  apiKey?: string; // or set THOTH_API_KEY env var
  apiUrl?: string; // required, or set THOTH_API_URL env var
  failOpen?: boolean; // default: false; retryable failures can allow when true
  stepUpTimeoutMinutes?: number; // default: 15
  stepUpPollIntervalMs?: number; // default: 5000
  sessionIntent?: string; // optional HIPAA minimum-necessary session scope
  policyContext?: Record<string, unknown>; // optional metadata.policy_context payload
  enforcementTraceId?: string; // default: generated session UUID
  environment?: string; // default: "prod"
}

Fields

FieldTypeDefaultDescription
agentIdstringrequiredUnique identifier for this agent
approvedScopestring[]requiredIntended tool names; server policy still applies
tenantIdstringrequiredYour Thoth tenant identifier
userIdstring"system"User initiating the request
enforcementEnforcementModeBLOCKHow to handle policy violations
apiKeystringprocess.env.THOTH_API_KEYAPI key for authentication
apiUrlstringprocess.env.THOTH_API_URLRequired tenant API base URL used for both /v1/enforce and /v1/events/batch
failOpenbooleanfalsePermit execution on retryable enforcer failure when enabled
stepUpTimeoutMinutesnumber15Max wait time for step-up approval
stepUpPollIntervalMsnumber5000Step-up polling interval in milliseconds
sessionIntentstringunsetSession purpose used for HIPAA minimum-necessary checks
policyContextRecord<string, unknown>unsetTenant policy context sent as metadata.policy_context
enforcementTraceIdstringgenerated session UUIDCorrelation ID propagated to enforcer/services
environmentstring"prod"Environment tag for env-scoped policy resolution

EnforcementMode

enum EnforcementMode {
  OBSERVE = "observe", // log only, never block
  STEP_UP = "step_up", // require human approval
  BLOCK = "block", // immediately reject violations
  PROGRESSIVE = "progressive", // escalating enforcement
}

ThothClient

ThothClient is a compatibility facade that stores default config values and delegates to module-level APIs.

class ThothClient {
  constructor(defaults?: Partial<ThothConfig>);
 
  instrument<T extends object>(agent: T, config?: Partial<ThothConfig>): T;
  instrumentAnthropic(
    toolFns: Record<string, AnthropicToolFn>,
    config?: Partial<ThothConfig>,
  ): Record<string, AnthropicToolFn>;
  instrumentOpenAI(
    toolFns: Record<string, OpenAIToolFn>,
    config?: Partial<ThothConfig>,
  ): Record<string, OpenAIToolFn>;
 
  // Legacy aliases:
  wrap<T extends object>(agent: T, config?: Partial<ThothConfig>): T;
  wrapAnthropicTools(
    toolFns: Record<string, AnthropicToolFn>,
    config?: Partial<ThothConfig>,
  ): Record<string, AnthropicToolFn>;
  wrapOpenAITools(
    toolFns: Record<string, OpenAIToolFn>,
    config?: Partial<ThothConfig>,
  ): Record<string, OpenAIToolFn>;
}

instrument()

Instrument a generic AI agent with Thoth governance. Wraps all tools with enforce/emit hooks. Returns the same agent object (mutated in-place).

function instrument<T extends object>(agent: T, config: ThothConfig): T;

Agent shape

The agent object must have a tools array where each element has a name: string and run: Function:

const agent = {
  tools: [
    {
      name: "search_docs",
      run: async (query: string) => `results for: ${query}`,
    },
    {
      name: "submit_payment",
      run: async (invoiceId: string) => `payment submitted: ${invoiceId}`,
    },
  ],
};

Example

import {
  instrument,
  EnforcementMode,
  ThothPolicyViolation,
} from "@atensec/thoth";
 
const agent = {
  tools: [
    { name: "search_docs", run: async (q: string) => `docs: ${q}` },
    { name: "submit_payment", run: async (id: string) => `paid: ${id}` },
  ],
};
 
instrument(agent, {
  agentId: "invoice-processor-v2",
  approvedScope: ["search_docs"], // submit_payment is out of scope
  tenantId: "acme-corp",
  apiUrl: "https://enforce.acme-corp.atensecurity.com",
  enforcement: EnforcementMode.BLOCK,
});
 
// Intended tool: server policy must permit this call
const result = await agent.tools[0].run("quarterly report");
 
// Refused or pending-approval call: handled according to the server decision
try {
  await agent.tools[1].run("INV-001");
} catch (err) {
  if (err instanceof ThothPolicyViolation) {
    console.error(`Blocked: ${err.reason} (violation_id=${err.violationId})`);
  }
}

Async generators

instrument() detects async generator functions automatically and wraps them correctly, yielding all chunks while still emitting pre/post behavioral events:

const streamingTool = {
  name: "stream_response",
  async *run(query: string) {
    for (const chunk of chunks) {
      yield chunk;
    }
  },
};

Failure behavior

With the default failOpen: false, policy checks fail closed: if /v1/enforce is unreachable or returns a non-OK response, the decision falls back to BLOCK (reason: "enforcer unavailable"), and instrument() will throw ThothPolicyViolation before running the tool.


emitBehavioralEvent()

Emit a single behavioral event directly to the Thoth API. Fire-and-forget — never blocks execution.

import { emitBehavioralEvent } from "@atensec/thoth";
// also available as a named import from the package
 
async function emitBehavioralEvent(
  event: BehavioralEvent,
  apiUrl: string,
  apiKey: string,
): Promise<void>;

The function catches all errors internally and never throws. If the API is unreachable, the error is silently discarded to preserve agent availability.

BehavioralEvent schema

interface BehavioralEvent {
  eventId: string; // UUID — generated automatically by instrument()
  tenantId: string;
  agentId?: string;
  sessionId: string;
  toolName?: string;
  userId: string;
  sourceType: SourceType; // "agent_tool_call" for SDK-wrapped tools
  eventType: EventType; // "TOOL_CALL_PRE" | "TOOL_CALL_POST" | "LLM_INVOCATION"
  content: string; // JSON-serialized arguments or result
  metadata?: Record<string, unknown>;
  approvedScope: string[];
  enforcementMode: EnforcementMode;
  sessionToolCalls: string[]; // tool names called so far in this session
  occurredAt: Date;
}

wrapAnthropicTools()

Wrap tool execution functions for use in an Anthropic Claude agentic loop. Returns a new map of governed callables.

import { wrapAnthropicTools } from "@atensec/thoth/anthropic";
 
function wrapAnthropicTools(
  toolFns: Record<string, AnthropicToolFn>,
  config: ThothConfig,
): Record<string, AnthropicToolFn>;
 
type AnthropicToolFn = (
  input: Record<string, unknown>,
) => unknown | Promise<unknown>;

Example

import Anthropic from "@anthropic-ai/sdk";
import { wrapAnthropicTools } from "@atensec/thoth/anthropic";
 
const client = new Anthropic();
 
const toolFns = {
  search_docs: async (input: Record<string, unknown>) => {
    const query = input.query as string;
    return `docs for: ${query}`;
  },
};
 
const governed = wrapAnthropicTools(toolFns, {
  agentId: "support-bot",
  approvedScope: ["search_docs"],
  tenantId: "acme-corp",
  apiUrl: "https://enforce.acme-corp.atensecurity.com",
});
 
// Standard Anthropic agentic loop
const tools: Anthropic.Tool[] = [
  {
    name: "search_docs",
    description: "Search company documentation",
    input_schema: {
      type: "object" as const,
      properties: { query: { type: "string" } },
      required: ["query"],
    },
  },
];
 
const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "Find docs about expense reports" },
];
 
while (true) {
  const response = await client.messages.create({
    model: "claude-opus-4-5",
    max_tokens: 1024,
    tools,
    messages,
  });
 
  if (response.stop_reason === "end_turn") break;
 
  const toolResults: Anthropic.ToolResultBlockParam[] = [];
  for (const block of response.content) {
    if (block.type === "tool_use") {
      const fn = governed[block.name];
      if (fn) {
        const result = await fn(block.input as Record<string, unknown>);
        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: String(result),
        });
      }
    }
  }
 
  messages.push({ role: "assistant", content: response.content });
  messages.push({ role: "user", content: toolResults });
}

wrapOpenAITools()

Wrap tool execution functions for use in an OpenAI tool-calling loop.

import { wrapOpenAITools } from "@atensec/thoth/openai";
 
function wrapOpenAITools(
  toolFns: Record<string, OpenAIToolFn>,
  config: ThothConfig,
): Record<string, OpenAIToolFn>;
 
type OpenAIToolFn = (
  args: Record<string, unknown>,
) => unknown | Promise<unknown>;

Example

import OpenAI from "openai";
import { wrapOpenAITools } from "@atensec/thoth/openai";
 
const openai = new OpenAI();
 
const governed = wrapOpenAITools(
  {
    search_docs: async (args) => `docs for: ${args.query}`,
  },
  {
    agentId: "support-bot",
    approvedScope: ["search_docs"],
    tenantId: "acme-corp",
    apiUrl: "https://enforce.acme-corp.atensecurity.com",
  },
);
 
const response = await openai.chat.completions.create({
  model: "gpt-4o",
  tools: [
    {
      type: "function",
      function: {
        name: "search_docs",
        description: "Search company docs",
        parameters: {
          type: "object",
          properties: { query: { type: "string" } },
        },
      },
    },
  ],
  messages: [{ role: "user", content: "Find docs about expense reports" }],
});
 
for (const toolCall of response.choices[0].message.tool_calls ?? []) {
  const fn = governed[toolCall.function.name];
  if (fn) {
    const args = JSON.parse(toolCall.function.arguments) as Record<
      string,
      unknown
    >;
    const result = await fn(args); // governance runs here
    console.log(result);
  }
}

ThothPolicyViolation

Thrown when the enforcer blocks a tool call.

class ThothPolicyViolation extends Error {
  public readonly decisionReasonCode?: string;
  public readonly actionClassification?: string;
  public readonly authorizationDecision?: string;
  public readonly deferTimeoutSeconds?: number;
  public readonly stepUpTimeoutSeconds?: number;
  public readonly riskScore?: number;
  public readonly latencyMs?: number;
  public readonly packId?: string;
  public readonly packVersion?: string;
  public readonly ruleVersion?: number;
  public readonly regulatoryRegimes?: string[];
  public readonly matchedRuleIds?: string[];
  public readonly matchedControlIds?: string[];
  public readonly policyReferences?: string[];
  public readonly modelSignals?: string[];
  public readonly receipt?: Record<string, unknown>;
 
  constructor(
    public readonly toolName: string,
    public readonly reason: string,
    public readonly violationId?: string,
  )
}
import { ThothPolicyViolation } from "@atensec/thoth";
 
try {
  await governedTool(input);
} catch (err) {
  if (err instanceof ThothPolicyViolation) {
    console.error({
      tool: err.toolName,
      reason: err.reason,
      violationId: err.violationId, // correlate with Thoth evidence records
      decisionReasonCode: err.decisionReasonCode,
      actionClassification: err.actionClassification,
      riskScore: err.riskScore,
      packId: err.packId,
      matchedRuleIds: err.matchedRuleIds,
      modelSignals: err.modelSignals,
    });
    return { error: "Action blocked by policy" };
  }
  throw err;
}

EnforcementDecision

The enforcer response shape (returned from POST /v1/enforce):

interface EnforcementDecision {
  decision: DecisionType; // "ALLOW" | "BLOCK" | "STEP_UP" | "MODIFY" | "DEFER"
  authorizationDecision?: string;
  decisionReasonCode?: string;
  actionClassification?: string;
  reason?: string;
  violationId?: string;
  holdToken?: string; // present when decision === "STEP_UP"
  riskScore?: number;
  latencyMs?: number;
  packId?: string;
  packVersion?: string;
  ruleVersion?: number;
  regulatoryRegimes?: string[];
  matchedRuleIds?: string[];
  matchedControlIds?: string[];
  policyReferences?: string[];
  modelSignals?: string[];
  receipt?: Record<string, unknown>;
  modifiedToolArgs?: Record<string, unknown>; // present when decision === "MODIFY"
  modificationReason?: string; // present when decision === "MODIFY"
  deferReason?: string; // present when decision === "DEFER"
  deferTimeoutSeconds?: number; // present when decision === "DEFER"
  stepUpTimeoutSeconds?: number;
}
 
enum DecisionType {
  ALLOW = "ALLOW",
  BLOCK = "BLOCK",
  STEP_UP = "STEP_UP",
  MODIFY = "MODIFY",
  DEFER = "DEFER",
}

Environment Variables

VariableDescription
THOTH_API_KEYAPI key for hosted Thoth authentication
THOTH_API_URLRequired tenant API base URL for both policy checks and event ingestion
THOTH_LOG_LEVELOptional SDK decision-log level override (DEBUG, INFO, WARN, ERROR); falls back to LOG_LEVEL

The TypeScript SDK reads these environment variables automatically in Node.js environments. If config.apiUrl and THOTH_API_URL are both missing, instrument() throws at startup. When decision logging is enabled at debug level, SDK logs include hold_token for STEP_UP flows.


TypeScript strict mode

The SDK is compiled with strict: true. All exported types are narrowed — no implicit any. If you use instrument<T>(), the generic T preserves your agent's type through the instrumentation:

interface MyAgent {
  tools: Array<{ name: string; run: (input: string) => Promise<string> }>
  someOtherField: string
}
 
const agent: MyAgent = { tools: [...], someOtherField: "hello" }
const governed = instrument(agent, config)
// governed is still typed as MyAgent
console.log(governed.someOtherField)  // ✓ TypeScript happy