Agora
Reference

Configuration

Every configuration key across the Telescope ecosystem in one place — the core, watchers, dashboard UI, alerts, and AI packages, each with its config file, defaults, and the environment variables it expects.

Each Telescope package owns its own config/*.ts file, published by its configure command and authored with that package's defineConfig. This page consolidates every key.

The *-watchers, *-alerts, and *-ai providers also accept their config under a nested key on the main config/telescope.ts (watchers_config, alerts_config, ai_config) as a fallback — but the dedicated, published config file per package is the recommended path.

Core — config/telescope.ts

import { defineConfig, storage } from '@adonis-agora/telescope'

export default defineConfig({
  enabled: true,
  store: 'memory',
  stores: {
    memory: storage.memory({ limit: 1000 }),
    // lucid: storage.lucid({ connection: 'pg' }),
  },
  watchers: ['request', 'diagnostics'],
  extensions: [],
  redact: { enabled: true, keys: [] },
})
KeyTypeDefaultDescription
enabledbooleantrueMaster switch. false records nothing and starts no watcher.
storestring | TelescopeStore'memory'Which named driver in stores is active, or a store instance.
storesRecord<string, StoreProvider>{}Named storage drivers built with the storage factory (storage.memory / storage.lucid).
watchersWatcherName[]['request', 'diagnostics']Any of request, diagnostics, logs. Omit a name to disable it.
diagnostics{ exclude?, recordClaimed? }{ exclude: [], recordClaimed: false }Mute noisy lib:event keys with exclude; recordClaimed also records events a lib-specific watcher already recorded as a typed entry.
logs{ minLevel?, tags? }{ minLevel: 'trace', tags: [] }Tuning for the logs watcher: raise the floor ('warn' drops debug/info) and append tags to every log entry.
requestCapture{ maxBodyBytes?, skipBodyContentTypes?, skipBody? }offOpt-in request BODY capture. See Request body capture.
extensionsTelescopeExtension[][]Extensions registered at boot.
redact{ enabled?, keys?, perType? }{ enabled: true, keys: [], perType: {} }Sensitive-data redaction — masks sensitive keys in every entry's content before persistence. perType raises the numeric size bounds for one entry type.
samplingnumber | SamplingConfigrecord everythingTail-sampling on the write path (bare keep-rate, or per-type rules keeping errors/slow).
pulse{ enabled?, windowMs?, topN?, buckets?, slowRouteMs?, cards? }enabled, 1h windowThe Pulse health rollup + its /api/metrics/pulse route.
nPlusOne{ enabled?, threshold? }{ enabled: true, threshold: 3 }N+1 query-loop detection thresholds (read-only analysis).
stream{ enabled? }{ enabled: true }Live SSE streaming of newly-stored entries to the dashboard.
prune{ after?, keepLast?, intervalMs?, enabled? }off unless presentBackground retention pruner — see Retention & overload protection.
overload{ enabled?, maxEventLoopLagMs?, startupGraceMs? }enabled, 200msEvent-loop overload guard — see Retention & overload protection.
clientErrorsClientErrorsConfigdisabledPublic client-error ingestion endpoint (opt-in).
otelOtelConfigdisabledOTel export — ships recorded entries as OTLP spans/logs to a Collector. Off by default; its @opentelemetry/* peers are only imported when enabled.

Redaction is on by default. Every entry's content is scrubbed at the store boundary — a built-in set of sensitive keys (authorization, cookie, set-cookie, password, token, api_key, secret, client_secret, private_key, …) is masked with [REDACTED], case-insensitively, at any depth. Add your own with redact: { keys: ['ssn'] }, or disable it with redact: { enabled: false }. perType overrides only the NUMERIC bounds (maxDepth, maxStringLength, maxArrayLength, maxNodes, maxContentBytes) for one entry type — useful to let exception stacks keep a bigger byte budget than high-volume request entries, e.g. redact: { perType: { exception: { maxContentBytes: 64_000 } } }. Masking stays global on purpose: which keys are secret is a security invariant, not a per-type preference. See Built-in redaction.

Storage is config-driven: store picks one of the named drivers in stores. The memory driver (storage.memory({ limit })) is the bounded ring buffer; the lucid driver (storage.lucid({ connection? })) is a persistent SQL store — @adonisjs/lucid is an optional peer imported only when lucid is selected. See Storage drivers. The bare store: 'memory' shorthand still works with no stores map (uses maxEntries, default 1000).

Watchers — config/telescope_watchers.ts

import { defineConfig } from '@adonis-agora/telescope/watchers'

export default defineConfig({
  enabled: true,
  watchers: ['query'],
})
KeyTypeDefaultDescription
enabledbooleantrueMaster switch for per-technology watchers.
watchersWatcherName[]['query']Any of query, mail, cache, http-client, logs, queue, events, redis, profiling, schedule, queue-manager.
queryQueryWatcherConfigsee belowslowMs (500), captureBindings (false), ignoreConnections, normalize (true).
httpClientHttpClientWatcherConfigsee belowslowMs (1000), ignoreHosts, captureBodies (false).
profilingProfilingWatcherConfigsee belowslowMs (100), minDurationMs (0).
scheduleScheduleWatcherConfigsee belowslowMs (1000).
queueManagerQueueManagerWatcherConfig{ queues: [] }queues (required to surface anything — queue names cannot be discovered), adapter (which configured adapter to use).

Only query is on by default — it's the verified one. Enable the rest knowing some event contracts are best-effort (those packages aren't installed in this repo). Query bindings are redacted unless query.captureBindings is on, and profiling/schedule are user-driven (they publish opt-in helpers rather than tapping an event). And remember Lucid only emits db:query when its connection debug is on. See Watchers.

'logs' appears in both watcher lists — here and in config/telescope.ts, where it also takes a logs: { minLevel, tags } block. Enable it in exactly one place. Whichever provider boots first owns the logger tap; the other one warns and stays inert, so its options are simply ignored.

Dashboard UI — config/telescope_ui.ts

import { defineConfig } from '@adonis-agora/telescope/ui'

export default defineConfig({
  enabled: true,
  path: '/telescope',
  // authorize: (ctx) => {
  //   const { auth } = ctx as unknown as { auth: { user?: { isAdmin?: boolean } } }
  //   return auth.user?.isAdmin === true
  // },
  // credentials: { token: env.get('TELESCOPE_UI_TOKEN') },
})
KeyTypeDefaultDescription
enabledbooleantrueMaster switch. false registers no routes.
pathstring/telescopeURL prefix (normalized: leading slash, no trailing slash).
authorize(ctx) => AuthorizeResult | Promise<AuthorizeResult> (AuthorizeResult = boolean | { allowed, reason? })default policyAccess-decision hook. Allow outside prod, deny in prod unless a credential matches. A denial's 401 vs 403 normally comes from a request-shape heuristic; return { allowed: false, reason: 'unauthenticated' | 'forbidden' } to bypass it — see Dashboard auth.
credentials{ token?, basic? }{}Built-in token / HTTP Basic gate (ignored when authorize is set).
dashboardAuth{ secret, ttl?, login }unsetBuilt-in session login screen (fails closed at boot without secret/login).
replay{ enabled?, port?, timeoutMs? }disabledRequest replay from the dashboard — off by default (a captured POST/DELETE re-runs).
cpuProfiling{ armEnabled? }disabledWhether the console may ARM a CPU capture. Reading captured profiles is never gated by this.
queueActions{ enabled? }disabledWhether the queue console may retry or enqueue jobs. Reading it is never gated by this.
dashboard{ enabled?, path? }enabledToggles / re-mounts the @adonis-agora/telescope-ui SPA.

credentials.token allows Authorization: Bearer <token> or ?token=<token>; credentials.basic is { username, password }. See Dashboard auth.

Env: TELESCOPE_UI_TOKEN, TELESCOPE_UI_PASSWORD (your names — sourced via env.get).

Alerts — config/telescope_alerts.ts

import env from '#start/env'
import { defineConfig } from '@adonis-agora/telescope/alerts'

export default defineConfig({
  enabled: true,
  channels: [{ type: 'console' }],
  rules: [{ type: 'new-exception', window: '1h' }],
  every: '30s',
  cooldown: '15m',
  instanceId: 'telescope',
  // dashboardUrl: 'https://telescope.example.com/',
})
KeyTypeDefaultDescription
enabledbooleantrueMaster switch.
channelsChannelConfig[][{ type: 'console' }]{type:'slack',url} / {type:'webhook',url} / {type:'console'} / a channel object.
rulesAlertRule[][{ type: 'new-exception', window: '1h' }]new-exception, every-exception (fires on repeats too; window optional and display-only), exception-rate (+ threshold), and stateful metric-threshold rules (raise/auto-resolve).
dashboardUrlstringunsetExternal dashboard URL — enables Slack deep links.
everystring'30s'Poll cadence (ms/s/m/h/d).
cooldownstring'15m'Per-rule / per-family re-notify suppression.
instanceIdstring'telescope'Reporting instance id on every payload.
geoLookup(ip) => AlertGeoLocation | nullunsetOptional IP-to-geo resolver rendering a coarse Location on exception alerts. Telescope ships no geo database — see Geo-enrichment.

Duration strings are validated at boot — an unparseable every/cooldown/window throws (fail-closed). Valid units: ms, s, m, h, d. See Alerts.

Only the first rule of each exception type is evaluated — list two new-exception rules and the second is silently ignored. metric-threshold is the exception: every one of those runs. See When rules do and don't run.

Env: TELESCOPE_SLACK_WEBHOOK, TELESCOPE_ALERT_WEBHOOK (your names).

AI — config/telescope_ai.ts

import env from '#start/env'
import { defineConfig } from '@adonis-agora/telescope/ai'

export default defineConfig({
  apiKey: env.get('ANTHROPIC_API_KEY'),
  // model: 'claude-sonnet-4-6',
  // maxTokens: 1024,
})
KeyTypeDefaultDescription
enabledbooleantrueMaster switch. Treated as disabled when neither apiKey nor client resolves.
apiKeystring(from env)Anthropic API key — env.get('ANTHROPIC_API_KEY').
clientAnthropicMessagesClientunsetA host-supplied model client used verbatim (keeps the SDK optional).
modelstringclaude-sonnet-4-6Claude model id (claude-opus-4-8 / claude-haiku-4-5-20251001).
maxTokensnumber1024Hard cap on generated tokens per diagnosis.
cacheTtlMsnumber24hDiagnosis cache TTL (a family is diagnosed once, then served from cache).
timeoutMsnumber8000Per-diagnosis timeout the coordinator enforces; <= 0 disables it.

Env: ANTHROPIC_API_KEY (validate as Env.schema.string.optional()). See @adonis-agora/telescope/ai.

MCP — config/telescope_mcp.ts

import env from '#start/env'
import { defineConfig } from '@adonis-agora/telescope/mcp'

export default defineConfig({
  enabled: true,
  path: '/telescope/mcp',
  credentials: { token: env.get('TELESCOPE_MCP_TOKEN') },
  // tools: ['list_entries', 'get_trace', 'get_health'],
})
KeyTypeDefaultDescription
enabledbooleantrueMaster switch. false registers no routes.
pathstring/telescope/mcpURL the JSON-RPC endpoint mounts at.
authorizeAuthorizeHookdefault policySame guard as the UI/metrics API.
credentials{ token?, basic? }{}Built-in gate for the default policy.
toolsMcpToolName[]all sixWhich tools to expose. See @adonis-agora/telescope/mcp.

CPU profiling — config/telescope_cpu_profiling.ts

import { defineConfig } from '@adonis-agora/telescope/cpu_profiling'

export default defineConfig({
  enabled: true,
  // sampleRate: 0,
  // maxConcurrent: 2,
})
KeyTypeDefaultDescription
enabledbooleanfalseMaster switch. Off by default — a running sampling profiler slows the process it profiles.
sampleRatenumber0Fraction (0–1) of requests captured automatically. 0 means captures only happen when armed.
maxConcurrentnumber2Maximum concurrent captures, bounding overhead under load.
minDurationMsnumber5Captures shorter than this are discarded.
samplingIntervalMicrosnumber1000V8 sampling interval in microseconds — lower is finer-grained and costlier. Floored at 100.

Arming a capture from the dashboard additionally requires cpuProfiling.armEnabled in config/telescope_ui.ts. See CPU profiling.

Providers registered by configure

For reference, node ace configure @adonis-agora/telescope registers the core provider and — for each optional feature you pick at the prompt — its provider in adonisrc.ts. Every provider is a subpath of the one @adonis-agora/telescope package:

FeatureProvider import pathExtra wiring
core@adonis-agora/telescope/telescope_providerTelescopeMiddleware on server stack
watchers@adonis-agora/telescope/watchers_provider
ui@adonis-agora/telescope/ui_providerroutes registered at boot
alerts@adonis-agora/telescope/alerts_provider
ai@adonis-agora/telescope/ai_providerbinds TelescopeAiDiagnoser, publishes the diagnosis coordinator
mcp@adonis-agora/telescope/mcp_providerJSON-RPC route registered at boot
cpu_profiling@adonis-agora/telescope/cpu_profiling_providerpublishes the profiler the UI's profile routes read

One provider is not offered at the prompt and has to be added by hand, because it lives in a separate package you install yourself:

PackageProvider import pathServes
@adonis-agora/telescope-ui@adonis-agora/telescope-ui/telescope_ui_dashboard_providerthe React console under the UI prefix

Storage is not a separate package — the memory and lucid drivers are built into @adonis-agora/telescope and selected in config/telescope.ts. node ace configure @adonis-agora/telescope also publishes the create_telescope_entries_table migration the lucid driver needs (run node ace migration:run after switching store: 'lucid'). See Storage drivers.

On this page