On this page

ADF Studio writes structured log entries to the adf_logs table for runtime events — lambda executions, function calls, API serving requests, and trigger evaluations.

Log Entry Fields

FieldTypeDescription
idINTEGERAuto-incrementing entry ID
levelTEXTdebug, info, warn, or error
originTEXTSource of the log (e.g., timer, lambda, sys_lambda, serving, adf_shell)
eventTEXTEvent category (e.g., on_timer, api_request, execute, result)
targetTEXTSpecific target (e.g., system:lib/router.ts:onMessage, lib/api.ts:handler)
messageTEXTHuman-readable log message
dataTEXTOptional JSON payload with additional context
created_atINTEGERUnix timestamp (ms)

What Gets Logged

Lambda and Trigger Executions

When a trigger fires and executes a lambda, the runtime logs:

  • execute — Lambda started, with the trigger type and target
  • result — Lambda completed, with duration and any return value

Function Calls (sys_lambda)

Each sys_lambda tool invocation logs:

  • execute — Function call started, with source file and arguments
  • result — Function completed, with duration

API Serving

When an agent serves HTTP requests:

  • api_request — Incoming request with method, path, and query parameters
  • api_response — Response sent with status code and duration

Shell Commands

Shell tool executions log:

  • execute — Command summary with duration
  • parse_error — Parse failures
  • timeout — Commands that exceeded the timeout

Tool Calls

Tool-level logging includes:

  • sys_code — Execution results and errors with duration
  • sys_fetch — Middleware rejections, fetch errors, and timeouts
  • adf_call — Sandbox-to-tool call routing with error categories (EXCLUDED_TOOL, NOT_FOUND, DISABLED, REQUIRES_APPROVAL, etc.)

Mesh Delivery

Mesh message delivery logs:

  • Local delivery failures
  • HTTP delivery failures and non-2xx responses

Console Output

Code running in the sandbox (via sys_code, sys_lambda, or lambdas) can write to logs using console.log, console.warn, and console.error. These appear as log entries with the appropriate level.

Not here: token and cost data

Token counts and model cost do not live in adf_logs. The usage/cost channel is the llm.completed umbilical event, which carries input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens, cost_usd, and call_source for every completed model call (turns, compaction, and adf.model_invoke). See llm.* in the umbilical event catalog.

For turn calls only, the same usage is also persisted as JSON on the adf_loop.tokens column of the loop row that the turn produced — that is what GET /agents/:id/usage rolls up. model_invoke and compaction calls do not create loop rows, so their usage is on the llm.completed event but not in adf_loop.

Behavioral history: adf_audit

adf_audit is a separate table from adf_logs — compressed JSON snapshots of cleared history, not runtime log lines. Its source column is one of loop:<name> (a cleared segment of that loop’s transcript — the host stream is loop:main; start_seq/end_seq record its seq range), inbox_message/outbox_message (a single message; ref = message id), or file (a deleted file; ref = path). A bare loop source is legacy (pre-v29, single unnamed stream), so match the family with source = 'loop' OR source LIKE 'loop:%'. Batch inbox/outbox sources and NULL seq/ref columns appear only on legacy rows.

Logging Configuration

The logging section in the agent config controls log filtering and retention. All filtering happens before the SQLite INSERT, so filtered entries incur zero I/O cost.

{
  "logging": {
    "default_level": "info",
    "max_rows": 10000,
    "rules": [
      { "origin": "serving", "min_level": "error" },
      { "origin": "lambda*", "min_level": "warn" },
      { "origin": "adf_shell", "min_level": "info" }
    ]
  }
}

Fields

FieldTypeDefaultDescription
default_levelstring"info"Global minimum log level. Entries below this are dropped.
max_rowsnumber | null10000Ring buffer size. Old entries are trimmed when this limit is exceeded. null = unlimited.
rulesarray[]Per-origin overrides. First matching rule wins.

Rules

Each rule has:

  • origin — Glob pattern matched against the log entry’s origin (e.g., "serving", "lambda*", "sys_*")
  • min_level — Minimum level to keep for matching origins (debug, info, warn, error)

Rules are evaluated in order — the first match wins. If no rule matches, default_level applies.

Ring Buffer

The adf_logs table acts as a ring buffer. When max_rows is set, old entries are automatically trimmed. The trim runs every 100 inserts (amortized) to avoid per-insert overhead.

For high-throughput agents (e.g., relay ADFs handling 1k+ requests/second), set a restrictive default_level and/or lower max_rows to prevent the log table from becoming a bottleneck. Set max_rows: null for unlimited retention — useful when a custom lambda handles cleanup via the on_logs trigger.

Configuring at Runtime

Use sys_update_config to modify logging settings:

// Set default level
{ "path": "logging.default_level", "value": "warn" }

// Set per-origin rules
{ "path": "logging.rules", "value": [
  { "origin": "serving", "min_level": "error" }
]}

// Set max rows (null for unlimited)
{ "path": "logging.max_rows", "value": 50000 }

on_logs Trigger

The on_logs trigger fires when a matching log entry is written. This enables reactive patterns — alerting, log forwarding, anomaly detection — without polling.

{
  "on_logs": {
    "enabled": true,
    "targets": [
      {
        "scope": "system",
        "lambda": "lib/alerter.ts:onError",
        "filter": { "level": ["error"] },
        "batch_ms": 5000,
        "batch_count": 10
      }
    ]
  }
}

Or set it at runtime, in the same style as Configuring at Runtime:

// Enable the trigger
{ "path": "triggers.on_logs.enabled", "value": true }

// Append a target
{ "path": "triggers.on_logs.targets", "action": "append", "value": { "scope": "system", "lambda": "lib/alerter.ts:onError", "filter": { "level": ["error"] } } }

Like other sys_update_config writes, logging.* and triggers.on_logs.* changes are HIL-gated (your principal approves).

Anti-Recursion

Log entries produced by the on_logs trigger handler itself do not re-fire the trigger. This prevents infinite loops.

Filter Fields

FieldTypeDescription
levelstring[]Match log levels (e.g., ["warn", "error"])
originstring[]Glob patterns for origin (e.g., ["serving", "lambda*"])
eventstring[]Glob patterns for event (e.g., ["api_*"])

Lambda Event Object

When on_logs fires in system scope, the lambda receives:

FieldTypeDescription
typestring"log_entry"
scopestring"system"
timestampnumberEvent timestamp (epoch ms)
contentstringLog message
logLevelstringLog level (debug, info, warn, error)
logOriginstring | nullLog origin
logEventstring | nullLog event
logTargetstring | nullLog target

Querying Logs

Use db_query to inspect logs:

-- Recent errors
SELECT * FROM adf_logs WHERE level = 'error' ORDER BY id DESC LIMIT 20

-- All logs from a specific origin
SELECT * FROM adf_logs WHERE origin LIKE 'agent:Monitor%' ORDER BY id DESC

-- API serving activity
SELECT * FROM adf_logs WHERE event IN ('api_request', 'api_response') ORDER BY id DESC LIMIT 50

-- Logs after a specific ID (for polling)
SELECT * FROM adf_logs WHERE id > 1000 ORDER BY id ASC

Log Schema

The adf_logs table has indexes on level and origin for efficient filtering.

CREATE TABLE adf_logs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  level TEXT NOT NULL DEFAULT 'info',
  origin TEXT,
  event TEXT,
  target TEXT,
  message TEXT NOT NULL,
  data TEXT,
  created_at INTEGER NOT NULL
);
CREATE INDEX idx_adf_logs_level ON adf_logs(level);
CREATE INDEX idx_adf_logs_origin ON adf_logs(origin);

UI

The Logs tab in the Bottom Panel provides level and origin filtering, expandable JSON data payloads, and auto-refresh polling. Logs reload automatically when switching between ADF files.