AI Agent Mode
dtctl provides first-class support for AI coding agents with a structured JSON output mode, automatic environment detection, and a machine-readable command catalog.
Overview
The --agent (or -A) flag wraps all dtctl output in a structured JSON envelope:
dtctl get workflows --agent
This makes it straightforward for AI agents to parse responses, handle errors, and discover follow-up actions without scraping human-readable text.
Response Format
Successful responses
{
"ok": true,
"result": [
{
"id": "wf-abc123",
"name": "Daily Health Check",
"state": "enabled"
}
],
"context": {
"verb": "get",
"resource": "workflow",
"suggestions": [
"dtctl describe workflow wf-abc123",
"dtctl exec workflow wf-abc123"
]
}
}
Error responses
{
"ok": false,
"error": {
"code": "auth_required",
"message": "No valid authentication found. Run 'dtctl auth login' or configure a token.",
"suggestions": [
"dtctl auth login --context my-env --environment https://abc12345.apps.dynatrace.com",
"dtctl config set-credentials my-token --token <your-token>"
]
}
}
Error codes are stable identifiers that agents can match on programmatically (e.g. auth_required, not_found, forbidden, rate_limited).
Query results: the result.kind discriminator
In agent mode, dtctl query results are self-describing: the result payload
carries a kind field so a consumer always branches on one discriminator,
regardless of how big the result was. There are three kinds:
result.kind |
When | Payload |
|---|---|---|
records |
small result, returned inline | the rows under result.records |
result-file |
large result spilled to a file | a manifest: path, format, rows, bytes, column stats, sample_rows |
summary-only |
large result but the rows could not be written to disk | the same manifest minus path |
On a result-file result the rows are on disk, so read them with
dtctl inspect <path> — --head/--tail/
--page/--fields for bounded row access, --jq '<program>' to keep only the
matching rows (a streaming filter over the whole file; a large match set
re-spills via the same --spill* guard), --schema/--stats to re-derive the
profile, --list to recover a path that has aged out of context — instead of
re-querying Grail. On a summary-only result the rows are not on disk, so
context.suggestions carries the right next step for why the spill degraded: a
read-only filesystem steers you to re-query with --spill=never and a bound
(| fields … / | limit N, or --max-result-records N) so the inline result
stays small, while a one-off write failure suggests retrying with an explicit
--spill-to <path>.
{
"ok": true,
"envelope_version": 1,
"result": {
"kind": "result-file",
"path": "~/Library/Caches/dtctl/results/prod/q-7f3a9c.jsonl",
"format": "jsonl",
"rows": 84213,
"columns": [ { "name": "status", "type": "long", "nulls": 0, "min": 500, "max": 599 } ],
"sample_rows": [ /* first few rows */ ]
},
"context": {
"verb": "query", "resource": "logs", "total": 84213,
"decided": "spilled", "threshold_bytes": 51200, "measured_bytes": 16804000
}
}
The envelope carries envelope_version for forward compatibility. A consumer
MUST treat an unrecognised result.kind as opaque — don’t parse result, fall
back to the human-readable context (which always carries decided, total,
warnings, and suggestions). When Grail sampled the result, the per-column
stats move into a sample_stats block (each column tagged basis: "sample") so
sample-based figures can’t be misread as population truth.
The inline
kind: "records"envelope is emitted on the spill-aware path whenever agent mode emits JSON — including under--spill=never, which forces every row inline regardless of size but still as akind: "records"envelope (never a human table). Explicit non-JSON output (-o toon/csv/yaml) and--jqtransforms keep their requested shape and fall through to the plain{ "records": …, "metadata": … }output.
Auto-Detection
dtctl automatically enables agent mode when it detects it is running inside a known AI agent environment. Detection is based on the presence of specific environment variables:
| Environment Variable | Agent |
|---|---|
CLAUDECODE |
Claude Code |
OPENCODE |
OpenCode |
GITHUB_COPILOT |
GitHub Copilot |
CURSOR_AGENT |
Cursor |
KIRO |
Kiro |
JUNIE |
Junie |
OPENCLAW |
OpenClaw |
CODEIUM_AGENT |
Codeium / Windsurf |
TABNINE_AGENT |
Tabnine |
AMAZON_Q |
Amazon Q |
When auto-detected, agent mode is enabled without requiring the --agent flag.
Opting out
To disable auto-detection and get normal human-readable output:
dtctl get workflows --no-agent
Behavior
Agent mode implies --plain:
- No ANSI colors in output
- No interactive prompts (e.g. name disambiguation)
- No progress spinners or animations
This ensures output is always machine-parseable.
Command Catalog
AI agents can bootstrap their knowledge of dtctl using the built-in command catalog:
# Minimal overview -- verbs, resources, and subcommands only (defaults to TOON)
dtctl commands
# Brief catalog -- adds mutating status, access levels, flag types, and scopes
dtctl commands --brief -o json
# Full catalog -- detailed command descriptions, flag defaults, and global flags
dtctl commands --full -o json
# Human-readable how-to guide in Markdown
dtctl commands howto
The bare dtctl commands overview is ideal for including in an agent’s system prompt or initial context, giving it a complete map of available operations without consuming excessive tokens; step up to --brief or --full when more detail is needed.
Environment Inventory
Where dtctl commands answers “what can I run?”, dtctl inventory answers “what is there to query?” — run it before exploratory DQL:
dtctl inventory -o json
It reports (read-only, budgeted, 4 queries by default): which catalog objects are fetchable vs query-only (never fetch metrics or fetch smartscape.*), buckets, filter segments, a live entity-type census, and capabilities as present, absent (with the evidence checked, as structured {name, evidence} pairs — cite it instead of re-probing), or unknown (no verdict; not evidence of absence). The capability set is customizable via --definitions. See Environment Inventory.
Tips and Tricks
Name resolution
When agent mode is active, interactive name disambiguation is disabled. Use exact IDs instead of display names to avoid ambiguity:
# Prefer IDs in agent mode
dtctl describe workflow wf-abc123
# Names may fail if multiple resources share the same name
dtctl describe workflow "Daily Health Check"
All describe subcommands support agent mode, returning the full resource object in the JSON envelope:
dtctl describe workflow wf-abc123 --agent
dtctl describe slo my-slo -A
dtctl describe dashboard my-dash -o json -A
Dry-run
Use --dry-run to preview mutating operations without making changes:
dtctl apply -f workflow.yaml --dry-run
Diff
Use --diff to see what would change before applying:
dtctl apply -f workflow.yaml --diff
Verbose output
Use -v or --verbose for additional debugging information:
dtctl get workflows -v --agent
Environment variables
Configure dtctl without interactive commands:
export DTCTL_ENVIRONMENT="https://abc12345.apps.dynatrace.com"
export DTCTL_TOKEN="dt0s16.XXXXXXXX.YYYYYYYY"
dtctl get workflows --agent
Pipeline commands
Chain dtctl commands with standard Unix tools:
# Get all workflow IDs, then describe each one
dtctl get workflows -o json --agent | jq -r '.result[].id' | xargs -I{} dtctl describe workflow {} --agent
# Export query results for processing
dtctl query 'fetch logs | filter status == "ERROR" | limit 10' -o json --agent | jq '.result'