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: [] },
})| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch. false records nothing and starts no watcher. |
store | string | TelescopeStore | 'memory' | Which named driver in stores is active, or a store instance. |
stores | Record<string, StoreProvider> | {} | Named storage drivers built with the storage factory (storage.memory / storage.lucid). |
watchers | WatcherName[] | ['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? } | off | Opt-in request BODY capture. See Request body capture. |
extensions | TelescopeExtension[] | [] | 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. |
sampling | number | SamplingConfig | record everything | Tail-sampling on the write path (bare keep-rate, or per-type rules keeping errors/slow). |
pulse | { enabled?, windowMs?, topN?, buckets?, slowRouteMs?, cards? } | enabled, 1h window | The 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 present | Background retention pruner — see Retention & overload protection. |
overload | { enabled?, maxEventLoopLagMs?, startupGraceMs? } | enabled, 200ms | Event-loop overload guard — see Retention & overload protection. |
clientErrors | ClientErrorsConfig | disabled | Public client-error ingestion endpoint (opt-in). |
otel | OtelConfig | disabled | OTel 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'],
})| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch for per-technology watchers. |
watchers | WatcherName[] | ['query'] | Any of query, mail, cache, http-client, logs, queue, events, redis, profiling, schedule, queue-manager. |
query | QueryWatcherConfig | see below | slowMs (500), captureBindings (false), ignoreConnections, normalize (true). |
httpClient | HttpClientWatcherConfig | see below | slowMs (1000), ignoreHosts, captureBodies (false). |
profiling | ProfilingWatcherConfig | see below | slowMs (100), minDurationMs (0). |
schedule | ScheduleWatcherConfig | see below | slowMs (1000). |
queueManager | QueueManagerWatcherConfig | { 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') },
})| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch. false registers no routes. |
path | string | /telescope | URL prefix (normalized: leading slash, no trailing slash). |
authorize | (ctx) => AuthorizeResult | Promise<AuthorizeResult> (AuthorizeResult = boolean | { allowed, reason? }) | default policy | Access-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 } | unset | Built-in session login screen (fails closed at boot without secret/login). |
replay | { enabled?, port?, timeoutMs? } | disabled | Request replay from the dashboard — off by default (a captured POST/DELETE re-runs). |
cpuProfiling | { armEnabled? } | disabled | Whether the console may ARM a CPU capture. Reading captured profiles is never gated by this. |
queueActions | { enabled? } | disabled | Whether the queue console may retry or enqueue jobs. Reading it is never gated by this. |
dashboard | { enabled?, path? } | enabled | Toggles / 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/',
})| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch. |
channels | ChannelConfig[] | [{ type: 'console' }] | {type:'slack',url} / {type:'webhook',url} / {type:'console'} / a channel object. |
rules | AlertRule[] | [{ 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). |
dashboardUrl | string | unset | External dashboard URL — enables Slack deep links. |
every | string | '30s' | Poll cadence (ms/s/m/h/d). |
cooldown | string | '15m' | Per-rule / per-family re-notify suppression. |
instanceId | string | 'telescope' | Reporting instance id on every payload. |
geoLookup | (ip) => AlertGeoLocation | null | unset | Optional 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,
})| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch. Treated as disabled when neither apiKey nor client resolves. |
apiKey | string | (from env) | Anthropic API key — env.get('ANTHROPIC_API_KEY'). |
client | AnthropicMessagesClient | unset | A host-supplied model client used verbatim (keeps the SDK optional). |
model | string | claude-sonnet-4-6 | Claude model id (claude-opus-4-8 / claude-haiku-4-5-20251001). |
maxTokens | number | 1024 | Hard cap on generated tokens per diagnosis. |
cacheTtlMs | number | 24h | Diagnosis cache TTL (a family is diagnosed once, then served from cache). |
timeoutMs | number | 8000 | Per-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'],
})| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch. false registers no routes. |
path | string | /telescope/mcp | URL the JSON-RPC endpoint mounts at. |
authorize | AuthorizeHook | default policy | Same guard as the UI/metrics API. |
credentials | { token?, basic? } | {} | Built-in gate for the default policy. |
tools | McpToolName[] | all six | Which 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,
})| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Master switch. Off by default — a running sampling profiler slows the process it profiles. |
sampleRate | number | 0 | Fraction (0–1) of requests captured automatically. 0 means captures only happen when armed. |
maxConcurrent | number | 2 | Maximum concurrent captures, bounding overhead under load. |
minDurationMs | number | 5 | Captures shorter than this are discarded. |
samplingIntervalMicros | number | 1000 | V8 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:
| Feature | Provider import path | Extra wiring |
|---|---|---|
| core | @adonis-agora/telescope/telescope_provider | TelescopeMiddleware on server stack |
| watchers | @adonis-agora/telescope/watchers_provider | — |
| ui | @adonis-agora/telescope/ui_provider | routes registered at boot |
| alerts | @adonis-agora/telescope/alerts_provider | — |
| ai | @adonis-agora/telescope/ai_provider | binds TelescopeAiDiagnoser, publishes the diagnosis coordinator |
| mcp | @adonis-agora/telescope/mcp_provider | JSON-RPC route registered at boot |
| cpu_profiling | @adonis-agora/telescope/cpu_profiling_provider | publishes 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:
| Package | Provider import path | Serves |
|---|---|---|
@adonis-agora/telescope-ui | @adonis-agora/telescope-ui/telescope_ui_dashboard_provider | the 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.