Veefore docs

TypeScript SDK

The complete @veefore/sdk surface — protect, the client, typed errors and the assertion helpers.

bash
npm install @veefore/sdk

ESM and CommonJS builds with type declarations and no runtime dependencies. Works on Node 20.10+ and in any runtime with fetch and WebCrypto.

Running veefore in your project installs this package, writes its configuration and wraps your tools for you. See Automatic setup.

A convenience layer, never a security boundary

Every protected action is decided server-side by the gateway on every call. Nothing in this package can grant an agent authority it does not already have, and nothing here caches a permission.

#guard(toolKey, fn, options?)

The way most agents integrate. Wrap the function that performs a tool, and every call is decided by the gateway before your function runs.

ts
import { guard } from '@veefore/sdk';

const refundCustomer = tool({
  description: 'Refund a customer order',
  parameters: z.object({ orderId: z.string(), amount: z.number() }),
  execute: guard('refund_customer', async ({ orderId, amount }) =>
    payments.refunds.create({ orderId, amount }),
  ),
});
Decision What guard() does
Permitted, CLIENT tool Runs your function and returns its result
Permitted, brokered HTTP tool Returns the gateway's result without running your function
Refused Throws VeeforeBlockedError
Needs a human Throws VeeforeApprovalRequiredError, or waits with waitForApproval

The tool's first argument is sent as the policy parameters. The wrapped function always returns a Promise, so wrap async functions.

capabilitystringoptional

Defaults to tool.<toolKey>, which veefore init registers.

waitForApprovalboolean or { timeoutMs }optional

Wait for a human instead of throwing. Suits background jobs; in a chat turn, throwing lets the model tell the user.

describe(...args) => Partial<ActionRequest>optional

Adds policy inputs derived from the call, such as amount, currency, destination or purpose.

clientVeeforeClientoptional

Use this client instead of the shared one built from the environment.

ts
execute: guard('refund_customer', refund, {
  describe: ({ amount }) => ({ amount, currency: 'USD', purpose: 'Customer refund' }),
}),

Configuration comes from the VEEFORE_* environment variables. If they are not set, guard() reads the block veefore init writes to .env.local; values already in the environment always win.

#protect(agent, options)

For code that calls Veefore directly rather than through a tool function.

ts
import { protect } from '@veefore/sdk';

const agent = protect(myAgent, { name: 'finance-agent' });

Returns a Proxy over your object. Own properties and methods pass through untouched; veefore, act, evaluate, simulate and handshake are layered on top, and only where your object does not already define them.

namestringoptional

Display metadata. Never used for authorization — identity is agentId.

gatewayUrlstringrequired

From VEEFORE_GATEWAY_URL if not passed.

agentIdstringrequired

From VEEFORE_AGENT_ID.

privateKeystringrequired

Base64 PKCS#8 Ed25519. From VEEFORE_AGENT_PRIVATE_KEY. Never logged, never sent anywhere, never written to disk by the SDK.

keyIdstringrequired

From VEEFORE_AGENT_KEY_ID. Identifies which key, which is what makes rotation work.

audiencestringoptional

Bound into assertions. Must match the gateway's published audience exactly. Defaults to gatewayUrl.

declaredToolsstring[]optional

Tools you intend to use, reported at handshake so anything unauthorized is a startup warning rather than a mid-task failure.

eagerHandshakebooleanoptional

Opens the session at construction so misconfiguration surfaces immediately. Defaults to false to keep protect() synchronous.

requestTimeoutMsnumberoptional

Per-request timeout.

maxRetriesnumberoptional

Retries for transport errors only. Decisions are never retried.

fetchtypeof fetchoptional

Inject your own, for instrumentation or a custom agent pool.

onLog(entry: SdkLogEntry) => voidoptional

Structured diagnostics. Never receives secrets. The SDK does not write to stdout on its own.

Missing required configuration throws VeeforeConfigurationError at construction, naming exactly what is absent.

#createClient(options) and configFromEnv(env?)

ts
import { createClient, configFromEnv } from '@veefore/sdk';

const client = createClient({ name: 'worker' });   // env + overrides
const partial = configFromEnv();                   // just read the env

Use createClient when you have no agent object to wrap — a worker, a cron job, a service.

#VeeforeClient

#handshake()

ts
const info = await agent.handshake();

Returns HandshakeResult: agentId, mode, environment, organization, authorizedTools, capabilities, passportVersion, expiresAt.

Advisory. Useful for failing fast and for logging; not a permission grant, and not trusted on the next call.

#authenticate() and info

ts
await agent.veefore.authenticate();   // open a session without a full handshake
agent.veefore.info;                   // last HandshakeResult, or null

#act(request)

Performs the action. Throws when refused.

#evaluate(request)

Returns the ActionDecision as data. Throws only on transport and authentication failures, so a BLOCK comes back as a value.

#simulate(request)

evaluate with dryRun: true. Evaluates without executing.

#actWithApproval(request, { timeoutMs? })

Evaluates; if an approval is required, waits for it honouring pollAfterMs, then resubmits the identical request with the approvalId. The gateway re-validates everything.

#approvalStatus(approvalId) and waitForApproval(approvalId, options)

ts
const status = await agent.veefore.approvalStatus('apr_01ABC');
await agent.veefore.waitForApproval('apr_01ABC', {
  timeoutMs: 15 * 60 * 1000,
  pollIntervalMs: 5000,
});

#mcpCall(request)

ts
await agent.veefore.mcpCall({
  server: 'internal-tools',
  tool: 'search_tickets',
  arguments: { query: 'refund failed' },
});

#a2aInvoke(request)

ts
await agent.veefore.a2aInvoke({
  connection: 'a2a_partner_logistics',
  capability: 'shipment.query',
  action: 'query',
  payload: { trackingId: 'TRK-8812' },
});

#ActionRequest

ts
interface ActionRequest {
  tool: string;                  // registered tool key — required
  action?: string;
  capability?: string;
  parameters?: JsonObject;
  resource?: { key?: string; type?: string; attributes?: JsonObject };
  amount?: number | null;        // minor units; a policy input only
  currency?: string | null;
  destination?: string | null;
  purpose?: string | null;       // shown on the approval card — fill this in
  idempotencyKey?: string;
  approvalId?: string;
  dryRun?: boolean;
  metadata?: JsonObject;
}

#ActionDecision

ts
interface ActionDecision {
  actionId: string;
  requestId: string;
  traceId: string;
  mode: GatewayMode;
  decision: PolicyDecision;      // ALLOW BLOCK REQUIRE_APPROVAL STEP_UP_AUTH LIMIT MASK
  outcome: ActionOutcome;
  allowed: boolean;              // branch on this
  result: JsonObject | null;
  reason: string;
  explanation: JsonObject;
  approval: { required: boolean; approvalId: string | null; status: string | null;
              expiresAt: string | null; pollAfterMs: number | null } | null;
  risk: { level: string; score: number } | null;
  shadow: { decision: PolicyDecision; reason: string } | null;
  limits: { appliedAmount: number | null; maskedFields: string[] } | null;
  latencyMs: number;
  stages: StageResult[];
}

`decision` is not always the enforcement decision

In OBSERVE, decision is the effective outcome (ALLOW) and shadow.decision is what enforcement would have done. allowed already accounts for mode, which is why it is the field to branch on.

#Errors

Class Meaning Retry
VeeforeConfigurationError Required configuration missing no — fix config
VeeforeAuthenticationError Identity refused: rotated, revoked, replayed, bad audience no
VeeforeBlockedError Policy refused. Carries the decision never
VeeforeApprovalRequiredError Human needed. Carries approvalId not a retry — wait
VeeforeApprovalDeniedError A human said no no
VeeforeRateLimitError 429 yes, with backoff
VeeforeConnectionError Transport failure or timeout yes, with backoff
VeeforeError Base class depends
ts
import { VeeforeBlockedError } from '@veefore/sdk';

try {
  await agent.act(request);
} catch (error) {
  if (error instanceof VeeforeBlockedError) {
    error.decision;    // the full ActionDecision
    error.message;     // the operator-authored reason from the policy rule
  }
}

decisionToError(decision) converts a decision into the matching error class, which is useful if you used evaluate() and later decide you want to throw.

#Assertion helpers

Exported so you can build the wire protocol yourself:

ts
import {
  createAssertion,
  generateNonce,
  canonicalJson,
  ASSERTION_TTL_SECONDS,
} from '@veefore/sdk';

canonicalJson is the canonicalisation the server uses — sorted keys, no insignificant whitespace — so two implementations produce identical bytes and identical signatures. See Sign your own assertion.

#Retries, precisely

The SDK retries transport errors — connection failures, timeouts, 5xx — with exponential backoff, up to maxRetries.

It never retries a decision. A 403 is the server's answer, and the answer will not change. Retrying a refusal produces the same refusal and burns your rate limit.

Identity, authority, policy and enforcement for autonomous AI agents.