feat: agent-mode detection and behavior split#21
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an “agent mode” to dci that auto-detects agent-driven sessions (or can be forced/disabled via env/flags) and adjusts CLI defaults/output to keep stdout machine-parseable and token-efficient.
Changes:
- Implement agent-mode detection with documented precedence (env override → flags → known agent env vars → non-TTY stdout), plus
dci statusdiagnostics andagent=1User-Agent tagging. - Switch default output to JSON and suppress decorative onboarding/hints in agent mode (routing chatter to stderr where applicable).
- Add docs + skill guidance and unit tests covering detection/overrides/User-Agent/default output.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
skills/dci-cli/SKILL.md |
Documents how to enable/verify agent mode for the CLI skill. |
README.md |
Adds an Agent Mode section documenting detection and behavior changes. |
main.go |
Implements agent-mode detection, default output behavior, stderr/stdout split for hints, and User-Agent tagging. |
main_test.go |
Adds unit tests for agent-mode resolution, flag override parsing, User-Agent tagging, and default output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Heads-up for the agent-mode work (#10): TOON (Token-Oriented Object Notation) is now a registered When agent-mode detection picks a default compact format, it can select |
|
Thanks for the heads-up on TOON! I went to wire agent mode to The hook is trivial: agent mode picks its compact default via Generated by Claude Code |
Detect whether dci is driven by a human or an AI agent and adapt output accordingly (closes #10). Detection precedence: 1. DCI_AGENT_MODE env var (explicit override, always wins) 2. --agent / --no-agent flags (per-invocation override) 3. known agent env vars (CLAUDECODE, CURSOR_AGENT, KIRO_AGENT, ...) 4. non-TTY stdout (soft signal) In agent mode: - default --output flips from table to compact JSON - terminal color/decoration is disabled (NOCOLOR) - banners, hints, and onboarding route to stderr so stdout stays parseable - the User-Agent header carries agent=1 - the first-run onboarding banner is suppressed In human mode, today's behavior is preserved. When an agent env var is detected but the caller opted out, a one-line stderr tip points to the optimized path. `dci status` now reports whether agent mode is active and why. Documented in README and the dci-cli skill.
Address PR review feedback: - agentFlagOverride now honors every boolean value form cobra/pflag accepts (--agent=0/1, --no-agent=0/1, =true/false, case-insensitive), so the early os.Args scan agrees with cobra's later parse and --agent=0 correctly overrides heuristics before NOCOLOR / User-Agent side effects run. - DCI_AGENT_MODE only treats recognized boolean tokens as decisive; an unrecognized value (e.g. DCI_AGENT_MODE=2) is ignored rather than silently forcing agent mode, and run() warns about it on stderr. - Add test coverage for the =0/1 flag forms and unrecognized env/flag values.
184bfd2 to
e153048
Compare
|
TOON's on
Build's green and the TOON/agent-mode tests pass. (The two Generated by Claude Code |
The CLI now defaults agent-mode output to TOON, so the dci-cli skill recommends and demonstrates --output toon for agent workflows. JSON is retained where standard JSON is required (e.g. jq pipelines and the structured JSON report walkthrough).
Per PR review, the CLI's User-Agent must be a constant client identifier and must not vary by agent mode or end user. Drop the agent=1 tag so the DCI API always sees a stable dci-cli/<version> identifier.
Per PR review, re-add an interface identifier to the User-Agent but in a parseable, analytics-friendly form. The UA now always carries a mode=agent|interactive token (present in both modes, no client prefix) so traffic can be grouped by interface across clients. The token reflects only the interface, never the end user, so the value stays a stable client identifier.
Decision log: User-Agent interface identifierCapturing the discussion and the final decision on how the CLI's How we got here
Research — UA conventions
DecisionAlways emit a self-describing
Detection basis | Token
-- | --
--agent / DCI_AGENT_MODE=1, or a known AI-agent env var | mode=agent
Non-TTY soft signal (pipe / redirect / CI/CD) | mode=noninteractive
Human at a TTY (or explicit human mode) | mode=interactive
Rationale:
Implemented in |
The non-TTY soft signal (pipes, redirects, CI/CD) enables agent-mode behavior but isn't a confirmed AI agent. Tagging it mode=agent would conflate genuine agent traffic with CI and piped use in analytics. Derive a three-value mode= token from the detection reason: agent (only for explicit --agent/DCI_AGENT_MODE=1 or a known AI-agent env var), noninteractive (non-TTY soft signal), and interactive (human TTY or explicit human mode). Runtime behavior is unchanged.
agentFlagOverride treated --agent=false as forcing human mode and --no-agent=false as forcing agent mode, and accepted yes/on/off tokens that pflag rejects — contradicting both pflag and the function's own 'agrees with cobra's later parse' contract. --agent and --no-agent are two independent pflag bool flags, so an explicit false just leaves that flag unset rather than flipping to the opposite mode. Track each flag's final value with last-write-wins, parse values with strconv.ParseBool (exactly as pflag does), and resolve a genuine --agent --no-agent conflict by most-recently-enabled-wins. parseBoolish stays lenient but is now used only for the DCI_AGENT_MODE env var.
Closes #10.
dcinow detects whether it's being driven by a human or an AI agent and adapts its output, following the spec in the Agentic UX series.Detection (priority order — first match wins)
DCI_AGENT_MODEenv var —=1forces agent mode,=0forces human mode. Always wins.--agent/--no-agentflags — explicit per-invocation override (last occurrence wins).CLAUDECODE,CLAUDE_CODE,CURSOR_AGENT,KIRO_AGENT,AIDER_SESSION,GEMINI_CLI,REPLIT_AGENT,WINDSURF_AGENT,OPENHANDS_AGENT,DEVIN_AGENT. The list is documented and PR-extensible.Behavior in agent mode
--outputflips fromtableto the compact, token-efficient TOON format (from Token-efficient output format #9)NOCOLOR)Human mode is unchanged. When an agent env var is detected but the caller opted out (
--no-agent/DCI_AGENT_MODE=0), a one-line stderr tip points to the optimized path (per the issue addendum).dci statusnow shows whether agent mode is active and the reason.Note on User-Agent
The
User-Agentcarries an interface identifier so DCI API traffic can be segmented by howdciis driven. After review discussion (@apgiorgi), the original opaqueagent=1tag was replaced with a self-describingmode=token. Because the non-TTY soft signal (pipes, redirects, CI/CD) enables agent-mode behavior without being a confirmed AI agent, the token is derived from the detection reason and has three values so analytics don't conflate them:mode=interactive--agent/DCI_AGENT_MODE=1, or a known AI-agent env varmode=agentmode=noninteractiveExample:
dci-cli/<version> (<go>; <os>/<arch>; mode=agent). The key is unprefixed (mode=, notdci-mode=) so the same dimension can be grouped across DoiT clients/interfaces, and it reflects only the interface — never the end user. Full rationale and research in the decision-log comment.Acceptance criteria
DCI_AGENT_MODEenv var toggles agent behavior--agent/--no-agentflags override env detectionUser-Agentcarries a self-describingmode=interactive|agent|noninteractivetoken (interface only, never the end user)Testing
TestResolveAgentMode(asserts both enablement and themode=classification),TestAgentFlagOverride,TestBuildUserAgent,TestDefaultOutputFormat,TestDetectedAgentEnvTestCustomerContextFlag/*) that require network access to load the OpenAPI spec — they fail identically onmainin this sandbox.Docs
dci-cliskill recommends agent-mode usage and defaults its examples to--output toonGenerated by Claude Code