Skip to content

feat: agent-mode detection and behavior split#21

Merged
apgiorgi merged 7 commits into
mainfrom
claude/issue-10-FdY2O
Jun 9, 2026
Merged

feat: agent-mode detection and behavior split#21
apgiorgi merged 7 commits into
mainfrom
claude/issue-10-FdY2O

Conversation

@apgiorgi

@apgiorgi apgiorgi commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Closes #10.

dci now 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)

  1. DCI_AGENT_MODE env var — =1 forces agent mode, =0 forces human mode. Always wins.
  2. --agent / --no-agent flags — explicit per-invocation override (last occurrence wins).
  3. Known agent env varsCLAUDECODE, 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.
  4. Non-TTY stdout — soft signal (pipe/redirect/CI) when nothing above applies.

Behavior in agent mode

  • Default --output flips from table to the compact, token-efficient TOON format (from Token-efficient output format #9)
  • Terminal color / decoration disabled (NOCOLOR)
  • Banners, hints, and first-run onboarding route to stderr so stdout stays parseable

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 status now shows whether agent mode is active and the reason.

Note on User-Agent

The User-Agent carries an interface identifier so DCI API traffic can be segmented by how dci is driven. After review discussion (@apgiorgi), the original opaque agent=1 tag was replaced with a self-describing mode= 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:

Detection basis Token
Human at a TTY (or explicit human mode) mode=interactive
--agent / DCI_AGENT_MODE=1, or a known AI-agent env var mode=agent
Non-TTY soft signal (pipe / redirect / CI/CD) mode=noninteractive

Example: dci-cli/<version> (<go>; <os>/<arch>; mode=agent). The key is unprefixed (mode=, not dci-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_MODE env var toggles agent behavior
  • --agent / --no-agent flags override env detection
  • Documented list of detected agent env vars; non-TTY stdout is a soft signal
  • All decorative output (color, hints) suppressed in agent mode
  • Stderr/stdout split documented and respected (data → stdout, chatter → stderr)
  • Agent-mode compact default uses the token-efficient TOON format (Token-efficient output format #9)
  • User-Agent carries a self-describing mode=interactive|agent|noninteractive token (interface only, never the end user)

Testing

  • New unit tests: TestResolveAgentMode (asserts both enablement and the mode= classification), TestAgentFlagOverride, TestBuildUserAgent, TestDefaultOutputFormat, TestDetectedAgentEnv
  • Full suite passes except two pre-existing integration tests (TestCustomerContextFlag/*) that require network access to load the OpenAPI spec — they fail identically on main in this sandbox.

Docs

  • README gains an Agent Mode section
  • The dci-cli skill recommends agent-mode usage and defaults its examples to --output toon

Generated by Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 status diagnostics and agent=1 User-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.

Comment thread main.go
Comment thread main.go
Comment thread main_test.go
@apgiorgi apgiorgi marked this pull request as ready for review June 8, 2026 15:55
@apgiorgi apgiorgi requested a review from eranchetz as a code owner June 8, 2026 15:55
@apgiorgi

apgiorgi commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Heads-up for the agent-mode work (#10): --output toon just landed for #9.

TOON (Token-Oriented Object Notation) is now a registered --output value alongside table|json|yaml|auto, implemented as a restish content type (dciToonContentType) that mirrors the existing table type and falls back to indented JSON on any encode error. table stays the default — selection is fully opt-in.

When agent-mode detection picks a default compact format, it can select toon (set rsh-output-format to "toon", or pass --output toon). No schema/projection/truncation changes were made — those remain open on #9 and are deliberately out of scope here.

apgiorgi commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the heads-up on TOON! I went to wire agent mode to toon, but I can't find it in the repo yet — there's no toon / dciToonContentType on main (currently at c11b52d) or in any open branch, and the --output whitelist is still table|json|yaml|auto. Switching the agent-mode default to toon before the content type is registered would make agent output error/fall back, so I've left the default as compact json for now.

The hook is trivial: agent mode picks its compact default via defaultOutputFormat(), so once toon lands on main it's a one-line change there (plus adding toon to the --output validation). Happy to follow up with that PR the moment it merges — just let me know if it's tracked elsewhere and I'm looking in the wrong place.


Generated by Claude Code

claude added 2 commits June 9, 2026 09:15
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.
@apgiorgi apgiorgi force-pushed the claude/issue-10-FdY2O branch from 184bfd2 to e153048 Compare June 9, 2026 09:16

apgiorgi commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

TOON's on main now (#9), so I've wired agent mode to it. Rebased this branch on top of the merge and made the switch:

  • defaultOutputFormat() now returns toon in agent mode (was json); humans still get table.
  • --output help text and the --agent flag description updated to mention the TOON default.
  • README's Agent Mode section now says the default --output becomes toon.
  • TestDefaultOutputFormat updated to expect toon; the --output validation already accepted toon from Token-efficient output format #9.

Build's green and the TOON/agent-mode tests pass. (The two TestCustomerContextFlag failures in this sandbox are network-only — they try to reach api.doit.com for API-type detection — and are unrelated to this change.)


Generated by Claude Code

Comment thread main.go Outdated
claude added 2 commits June 9, 2026 09:27
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comment thread main.go
Comment thread main_test.go
Comment thread main.go Outdated
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.

apgiorgi commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Decision log: User-Agent interface identifier

Capturing the discussion and the final decision on how the CLI's User-Agent signals how the CLI is being driven, since it changed a few times during review.

How we got here

  1. Original PR: agent mode appended an opaque agent=1 tag to the UA, present only in agent mode.
  2. Review (@apgiorgi): the UA "should always be the same, independently of the final user." → I removed the tag entirely and made the UA a constant identifier.
  3. Follow-up (@apgiorgi): an interface identifier is wanted ("I like the addition… 'agent=1' is not ideal — is there a convention, something more parseable? Think about analytics"). → re-add it, but in an analytics-friendly form.
  4. Refinement: flagged that CI/CD pipelines and piped/redirected output enable agent-mode behavior via the non-TTY soft signal — so a binary agent/human token would conflate genuine AI-agent traffic with CI and dci … | jq. → split into three honest buckets.

Research — UA conventions

  • RFC 9110 §10.1.5 defines User-Agent as product/version tokens optionally followed by a parenthesized comment of ;-separated items (e.g. Mozilla/5.0 (X11; Linux x86_64) …).
  • Embedding metadata is conventionally done either as extra product tokens (curl/8.6 libcurl/8.6) or as key=value items inside the comment. Major SDKs use the comment approach — e.g. the AWS SDK appends md/GOOS#linux …, Google API clients append gl-go/… gccl/…. key=value in the comment is the most log/analytics-pipeline-friendly: it's stable, position-independent, and trivially regex/split-parsable.
  • Why agent=1 is weak for analytics: (a) it's only present in agent mode, so "human" is indistinguishable from "tag absent / older client"; (b) the value 1 is opaque and not self-describing.

Decision

Always emit a self-describing mode= token in the UA comment. Because the non-TTY soft signal enables agent-mode behavior without being a confirmed AI agent, the token has three values, derived from why the mode was chosen:

mode=agent dci-cli/<version> (<go>; <os>/<arch>; mode=agent)
mode=interactive dci-cli/<version> (<go>; <os>/<arch>; mode=interactive)
mode=noninteractive dci-cli/<version> (<go>; <os>/<arch>; mode=noninteractive)
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:

  • Always present + explicit values → clean GROUP BY mode in analytics; no inferring from absence.
  • Three buckets, not two  mode=agent cleanly means "identified AI agent"; CI/pipe traffic is its own noninteractive bucket instead of polluting the agent metric. Runtime behavior is unchanged — CI/pipes still get the compact agent-style output, they're just labelled honestly.
  • Unprefixed key (mode=, not dci-mode=) → per @apgiorgi, the same mode dimension can be grouped across other DoiT clients/interfaces too, to compare usage across the product surface — not just this CLI.
  • Interface only, never the end user → satisfies the earlier constraint that the UA must not vary per final user. The token reflects how dci was driven, nothing about who is running it.

Implemented in c2a09d1 (initial mode= token) and refined in a49ec32: buildUserAgent(uaMode), resolveAgentMode now returns the classification, updated call site/comments, README bullet, and tests (TestResolveAgentMode asserts the bucket for every case; TestBuildUserAgent covers all three values plus the empty-default fallback).

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.

@apgiorgi apgiorgi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comment thread main.go Outdated
Comment thread main_test.go Outdated
Comment thread main_test.go Outdated
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.

@apgiorgi apgiorgi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@apgiorgi apgiorgi merged commit 573c4c4 into main Jun 9, 2026
6 checks passed
@apgiorgi apgiorgi deleted the claude/issue-10-FdY2O branch June 9, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent-mode detection and behavior split

3 participants