GOAT Network
AgentKit

Runtime Configuration

The ExecutionRuntime is AgentKit's production-grade execution engine. Every action call flows through a pipeline of safety and observability layers:

Execution Pipeline
Input Validation (Zod / JSON Schema)
  → Policy Gate (network, risk level, write permissions)
    → Idempotency Check (memory or Redis)
      → Action Execution (with retry + timeout)
        → Output Validation
          → Metrics Recording
            → Hook Callbacks

ExecutionRuntime

Create a runtime by passing a PolicyEngine and an optional configuration object:

src/runtime.ts
import { PolicyEngine, ExecutionRuntime } from '@goatnetwork/agentkit/core';

const policy = new PolicyEngine({
  allowedNetworks: ['goat-testnet'],
  maxRiskWithoutConfirm: 'low',
  writeEnabled: true,
});

const runtime = new ExecutionRuntime(policy, {
  maxRetries: 2,
  retryDelayMs: 200,
  defaultTimeoutMs: 30_000,
  noRetryHighRiskWrites: true,    // default: true
  validateOutput: true,            // default: true
});

ExecutionConfig Reference

OptionTypeDefaultDescription
maxRetriesnumber2Maximum retry attempts on transient failures
retryDelayMsnumber200Base delay before first retry (exponential backoff: delay * 2^(attempt-1))
defaultTimeoutMsnumberundefinedGlobal timeout for all actions (overridden by per-call timeoutMs)
noRetryHighRiskWritesbooleantrueSkip retries for non-read actions to prevent duplicate state changes
validateOutputbooleantrueValidate action output against zodOutputSchema or outputSchema
loggerRuntimeLoggerconsoleLoggerStructured JSON logger
metricsRuntimeMetricsNoopRuntimeMetricsMetrics collector
idempotencyStoreIdempotencyStoreInMemoryIdempotencyStoreIdempotency backend
idempotencyTtlSecondsnumber3600TTL for idempotency entries
hooksExecutionHooksundefinedLifecycle callbacks

ExecutionResult

Every runtime.run() call returns a typed result:

ExecutionResult
interface ExecutionResult<T = unknown> {
  ok: boolean;           // true if action succeeded
  output?: T;            // Action output (only when ok: true)
  error?: string;        // Error message (only when ok: false)
  errorCode?: ErrorCode; // Structured error code
  traceId: string;       // From the input context
  action: string;        // Action name
  attempts: number;      // Total execution attempts
}

Policy Engine

The PolicyEngine evaluates four checks before allowing an action to execute:

  1. Network allowlist -- is the request's network in allowedNetworks?
  2. Action network support -- does the action support this network?
  3. Write permissions -- if writeEnabled is false, all non-read actions are blocked
  4. Risk level gate -- does the action's risk exceed maxRiskWithoutConfirm without explicit confirmation?
Policy configuration
const policy = new PolicyEngine({
  allowedNetworks: ['goat-testnet', 'goat-mainnet'],
  maxRiskWithoutConfirm: 'medium',  // 'read' | 'low' | 'medium' | 'high'
  writeEnabled: true,
});

Risk Level Hierarchy

read (0) < low (1) < medium (2) < high (3)

If maxRiskWithoutConfirm is set to 'low', any action with risk medium or high requires { confirmed: true } in the execution options.

Actions with requiresConfirmation: true always need confirmation regardless of the policy setting.

Policy Blocked Result

Blocked result
{
  ok: false,
  errorCode: 'POLICY_BLOCKED',
  error: 'Policy blocked: Confirmation required for risk level: high',
  traceId: 'trace_001',
  action: 'wallet.transfer_native',
  attempts: 0
}

Idempotency

Idempotency prevents duplicate execution when the same action is retried with the same key. AgentKit supports two backends:

Default: in-memory
import { InMemoryIdempotencyStore } from '@goatnetwork/agentkit/core';

const runtime = new ExecutionRuntime(policy, {
  idempotencyStore: new InMemoryIdempotencyStore(),
});
Redis (distributed)
import Redis from 'ioredis';
import { RedisIdempotencyStore } from '@goatnetwork/agentkit/core';

const redis = new Redis('redis://127.0.0.1:6379');
const runtime = new ExecutionRuntime(policy, {
  idempotencyStore: new RedisIdempotencyStore(redis),
});

Environment-Based Factory

You can also configure the store via environment variables:

Auto-detect from env
import { createIdempotencyStoreFromEnv } from '@goatnetwork/agentkit/core';

const { store, mode } = createIdempotencyStoreFromEnv();
// Reads AGENTKIT_IDEMPOTENCY_MODE (memory | redis) and AGENTKIT_REDIS_URL

Using Idempotency Keys

Pass an idempotencyKey in the execution options:

Idempotent execution
const result = await runtime.run(
  provider.get('goat.x402.payment.create'),
  context,
  { to: '0xabc...', asset: 'USDC', amount: '10' },
  { confirmed: true, idempotencyKey: 'payment-order-12345' }
);
// Repeated calls with the same key return the cached result

The Redis backend uses atomic SET NX for lock acquisition and a Lua script for safe lock release, preventing race conditions in distributed deployments.


Retries and Timeouts

Retry Behavior

  • Uses exponential backoff: retryDelayMs * 2^(attempt - 1)
  • Non-retryable errors (validation, policy, not-found, aborted) break immediately
  • When noRetryHighRiskWrites is true (default), non-read actions skip retries entirely

Per-Action Timeout

Timeout options
const result = await runtime.run(
  provider.get('bridge.withdraw'),
  context,
  input,
  { confirmed: true, timeoutMs: 60_000 }  // 60-second timeout for this call
);

Timeout uses Promise.race and aborts the action's signal on expiry.


Prometheus Metrics

AgentKit has built-in Prometheus metrics support:

src/metrics-server.ts
import { createServer } from 'node:http';
import {
  PolicyEngine,
  ExecutionRuntime,
  InMemoryRuntimeMetrics,
} from '@goatnetwork/agentkit/core';
import { renderPrometheus } from '@goatnetwork/agentkit/core';

const metrics = new InMemoryRuntimeMetrics();

const runtime = new ExecutionRuntime(policy, {
  metrics,
  maxRetries: 0,
});

// Expose /metrics endpoint
const port = Number(process.env.AGENTKIT_METRICS_PORT ?? 9464);

createServer(async (req, res) => {
  if (req.url === '/metrics') {
    res.setHeader('content-type', 'text/plain; version=0.0.4');
    res.end(renderPrometheus(metrics));
    return;
  }
  res.statusCode = 404;
  res.end('not found');
}).listen(port, () => {
  console.log(`Metrics at http://127.0.0.1:${port}/metrics`);
});

Exported Metrics

MetricTypeLabelsDescription
agentkit_runtime_counter_totalcountermetric, action, codeSuccess/error counts per action
agentkit_runtime_histogram_countgaugemetric, actionRequest count per action
agentkit_runtime_histogram_avggaugemetric, actionAverage latency in ms
agentkit_runtime_histogram_maxgaugemetric, actionMaximum latency in ms

Internal counter names: runtime.success, runtime.error, runtime.idempotency_hit

Internal histogram name: runtime.latency_ms


Execution Hooks

Hooks provide observation callbacks for every stage of action execution. They are ideal for logging, alerting, and audit trails.

src/hooks.ts
import type { ExecutionHooks } from '@goatnetwork/agentkit/core';

const hooks: ExecutionHooks = {
  onActionStart(event) {
    console.log(`[${event.traceId}] Starting ${event.action} (attempt ${event.attempt})`);
  },
  onActionSuccess(event) {
    console.log(`[${event.traceId}] ${event.action} succeeded in ${event.durationMs}ms`);
  },
  onActionError(event) {
    console.error(`[${event.traceId}] ${event.action} failed: ${event.errorCode} - ${event.errorMessage}`);
  },
  onPolicyBlocked(event) {
    console.warn(`[${event.traceId}] ${event.action} blocked: ${event.reason}`);
  },
};

const runtime = new ExecutionRuntime(policy, { hooks });

Hook Event Types

HookEvent Fields
onActionStarttraceId, action, attempt, input, timestamp
onActionSuccesstraceId, action, attempt, output, durationMs, timestamp
onActionErrortraceId, action, attempts, errorCode, errorMessage, durationMs, timestamp
onPolicyBlockedtraceId, action, reason, timestamp

Actions with sensitiveOutputFields automatically redact those fields from the output in onActionSuccess events. The original unredacted output is still returned in the ExecutionResult.


Error Codes

All runtime errors use structured error codes:

CodeDescriptionRetryable?
INVALID_INPUTZod/JSON Schema validation failedNo
INVALID_OUTPUTOutput validation failedNo
POLICY_BLOCKEDPolicy engine rejected the actionNo
ACTION_NOT_FOUNDAction name not registeredNo
ABORTEDOperation cancelled via AbortSignalNo
ADAPTER_REQUEST_FAILEDHTTP adapter request failedYes
WALLET_SIGN_FAILEDWallet signing operation failedYes
WALLET_TRANSFER_FAILEDWallet transfer operation failedYes
TIMEOUTAction exceeded timeoutYes
IDEMPOTENCY_CONFLICTAnother execution in progress for this keyYes
INTERNAL_ERRORUnexpected errorYes

Structured Logging

The default consoleLogger outputs structured JSON logs:

Log output
{
  "ts": "2025-01-15T10:30:00.000Z",
  "level": "info",
  "message": "action run start",
  "traceId": "trace_001",
  "action": "wallet.balance",
  "attempt": 1
}

You can provide a custom logger implementing the RuntimeLogger interface:

Custom logger
import type { RuntimeLogger } from '@goatnetwork/agentkit/core';

const myLogger: RuntimeLogger = {
  log(level, message, meta) {
    // Send to your logging infrastructure
    myLoggingService.send({ level, message, ...meta });
  },
};

const runtime = new ExecutionRuntime(policy, { logger: myLogger });

On this page