Skip to content

feat: add agent mode output foundation#69

Merged
lorenjphillips merged 1 commit into
mainfrom
feat/aci-1-agent-mode-foundation
May 19, 2026
Merged

feat: add agent mode output foundation#69
lorenjphillips merged 1 commit into
mainfrom
feat/aci-1-agent-mode-foundation

Conversation

@lorenjphillips
Copy link
Copy Markdown
Contributor

@lorenjphillips lorenjphillips commented May 19, 2026

Summary

  • Adds global --agent mode and a shared Open ACI-style envelope for success and error output.
  • Routes existing command output through OutputContext while preserving current human/table and --format json behavior.
  • Suppresses agent-hostile side channels for auth prompts, API-key warnings, audio downloads, not-found hints, and runs watch progress output.
  • Adds coverage for success envelopes, error envelopes, warnings, raw JSON compatibility, and quiet watch behavior.

Testing

  • cargo fmt
  • cargo clippy --all-targets -- -D warnings
  • cargo test

Stacked PR 1 of 5. No version bump or release changes.

Summary by CodeRabbit

  • New Features

    • Global --agent mode: structured, envelope-wrapped JSON responses (metadata, warnings, next actions) and standardized exit codes for CI/agent consumers.
    • Commands consistently emit machine-readable list/one/success envelopes and agent-formatted errors.
  • Behavior Changes

    • Agent mode disables interactive prompts and progress bars; human-mode output unchanged.
    • Auth/config now return masked API key info appropriately; audio download and run-watch behavior adapt to agent vs human modes.
  • Tests

    • Expanded coverage for agent-mode JSON envelopes and non-agent formatting.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba6d293d-2642-4b0a-8935-f12697a47b5c

📥 Commits

Reviewing files that changed from the base of the PR and between 68c64d9 and 68184a5.

📒 Files selected for processing (24)
  • src/cli.rs
  • src/client/models/conversation.rs
  • src/client/models/simulation.rs
  • src/commands/agents.rs
  • src/commands/api_keys.rs
  • src/commands/auth.rs
  • src/commands/config.rs
  • src/commands/conversations.rs
  • src/commands/dashboards.rs
  • src/commands/metrics.rs
  • src/commands/mutations.rs
  • src/commands/personas.rs
  • src/commands/review_annotations.rs
  • src/commands/review_projects.rs
  • src/commands/run_templates.rs
  • src/commands/runs.rs
  • src/commands/scheduled_runs.rs
  • src/commands/simulations.rs
  • src/commands/test_cases.rs
  • src/commands/test_sets.rs
  • src/config.rs
  • src/main.rs
  • src/output.rs
  • tests/cli_tests.rs
✅ Files skipped from review due to trivial changes (1)
  • src/client/models/conversation.rs
🚧 Files skipped from review as they are similar to previous changes (20)
  • src/client/models/simulation.rs
  • src/commands/agents.rs
  • src/commands/review_projects.rs
  • src/commands/api_keys.rs
  • src/commands/review_annotations.rs
  • src/commands/auth.rs
  • src/commands/config.rs
  • src/commands/dashboards.rs
  • src/main.rs
  • src/commands/mutations.rs
  • src/commands/test_cases.rs
  • src/commands/run_templates.rs
  • src/commands/test_sets.rs
  • src/output.rs
  • src/commands/simulations.rs
  • src/commands/metrics.rs
  • src/commands/conversations.rs
  • src/commands/runs.rs
  • src/commands/scheduled_runs.rs
  • src/cli.rs

📝 Walkthrough

Walkthrough

Adds a global --agent flag and OutputContext; introduces agent JSON envelopes and emit_* APIs; threads ctx through cli::run and command handlers; updates main to return ExitCode with agent-aware error emission; updates many commands to use emit_list/emit_one/emit_success and adds operation()/resource() helpers; expands tests for agent-mode JSON.

Changes

Agent Output Mode

Layer / File(s) Summary
Output infrastructure & agent types
src/output.rs, src/client/models/*
Adds ACI_VERSION, OutputContext, AgentWarning/AgentError/NextAction, agent envelopes, emit_list/emit_one/emit_one_with_warnings/emit_success/emit_error, and robust agent JSON serialization; adds Serialize to audio response models.
CLI args, run wiring, and main ExitCode
src/cli.rs, src/main.rs
Adds global --agent to Cli, adds Commands::resource/operation, changes run to run(cli, ctx), and changes main to return ExitCode with agent-aware parse/run error handling and exit-code mapping.
Auth and Config commands
src/commands/auth.rs, src/commands/config.rs, src/config.rs
login, whoami, and config accept &OutputContext, mask API keys, and emit agent-mode JSON; mask_api_key helper added.
Agents & API keys
src/commands/agents.rs, src/commands/api_keys.rs
Add operation() helpers; migrate outputs to emit_*; api-key create emits AgentWarning in agent mode.
Conversations (audio) & Dashboards/Widgets
src/commands/conversations.rs, src/commands/dashboards.rs
Use emit_*, add operation() helpers, thread show_progress into download_audio, and conditionally create indicatif progress bar when appropriate.
Metrics, Mutations, Personas
src/commands/metrics.rs, src/commands/mutations.rs, src/commands/personas.rs
Add operation() helpers and migrate all CRUD branches to emit_list/emit_one/emit_success.
Review Projects/Annotations & Run Templates
src/commands/review_projects.rs, src/commands/review_annotations.rs, src/commands/run_templates.rs
Add operation() mapping and migrate outputs to emit_* with OutputContext.
Runs, Scheduled Runs & Simulations
src/commands/runs.rs, src/commands/scheduled_runs.rs, src/commands/simulations.rs
Add operation() helpers; migrate outputs to emit_*; runs.watch supports agent-mode polling emitting completion; simulations audio flow uses show_progress and emits success or audio object.
Test Cases & Test Sets
src/commands/test_cases.rs, src/commands/test_sets.rs
Add operation() helpers; migrate output to emit_*; test-cases stdin bulk create emits aggregated JSON summary in agent mode and suppresses per-line stderr when agent-mode.
CLI tests
tests/cli_tests.rs
Add stdout_json helper and extensive agent-mode tests asserting envelope shape, warnings, errors, next_actions, and JSON outputs for audio and stdin flows.

Sequence Diagram (high level):

sequenceDiagram
  participant User as CLI invocation
  participant CLI as clap parsing & cli::run
  participant Command as command.execute
  participant Emit as emit_* functions
  participant Serde as serde_json
  participant Stdout as stdout
  User->>CLI: coval ... --agent
  CLI->>Command: command.execute(..., &OutputContext)
  Command->>Emit: emit_one(ctx, resource, operation, data)
  Emit->>Emit: check ctx.agent
  alt agent
    Emit->>Serde: serialize AgentEnvelope
    Serde->>Stdout: print pretty JSON
  else human
    Emit->>Stdout: format/table output
  end
Loading

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Poem:

"A rabbit hopped through code at night,
Whispered flags to make output right.
It wrapped the CLI in JSON light,
Emitted envelopes tidy and bright.
🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add agent mode output foundation' is specific, concise, and accurately summarizes the primary change—introducing agent mode with structured output support—which aligns with the extensive refactoring across multiple command files and the new OutputContext system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/aci-1-agent-mode-foundation

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli.rs (1)

158-217: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Bind the authenticated command once before the inner dispatch.

The outer match cli.command consumes cli.command, so the inner match cli.command in the fallback arm attempts to reuse a moved value and will not compile. Bind the fallback arm as command => { ... } and match on that binding instead.

Suggested fix
    match cli.command {
        Commands::Login(args) => commands::auth::login(args, ctx).await,
        Commands::Whoami => {
            commands::auth::whoami(api_key.as_ref(), ctx);
            Ok(())
        }
        Commands::Config { command } => commands::config::execute(command, ctx),
-        _ => {
+        command => {
             let api_key = api_key.ok_or_else(|| {
                 anyhow::anyhow!(
                     "Not authenticated. Run `coval login` or set COVAL_API_KEY environment variable."
                 )
             })?;
             let client = CovalClient::new(api_key, api_url.as_deref());

-            match cli.command {
+            match command {
                 Commands::Agents { command } => {
                     commands::agents::execute(command, &client, ctx).await
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.rs` around lines 158 - 217, The fallback arm currently re-uses the
moved value cli.command causing a compile error; change the outer match to bind
the arm (e.g., use `command => { ... }`) and then perform the inner dispatch by
matching on that binding (match command { Commands::Agents { command } =>
commands::agents::execute(...).await, ... }). Ensure you reference the same
symbols (cli.command, Commands::Agents/Conversations/Runs/... and functions like
commands::agents::execute) and construct the CovalClient before the inner match
using the bound `command`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/config.rs`:
- Around line 39-42: The current masking uses byte-index slicing on the API key
(see the closure mapping "api_key" => config.api_key.map(...) in config.rs and
the similar mapping in auth.rs) which can panic on multi-byte UTF-8 characters;
change both places to operate on Unicode scalar values by using character-based
operations: compute the character count, if >8 build the masked string by
collecting the first four chars (e.g., key.chars().take(4).collect()) and the
last four chars by taking from the end (e.g.,
key.chars().rev().take(4).collect() and reverse that result), otherwise return
the key as-is; keep the same mask format "{}...{}" and preserve the existing
closures/mapping structure (the unique symbols to edit are the "api_key" => ...
closure in config.rs and the analogous closure in auth.rs).

In `@src/commands/conversations.rs`:
- Around line 209-214: The None branch currently prints audio.audio_url directly
when ctx.agent is false; change it so only the human output path uses println!,
and all non-human/output-context flows use emit_one. Specifically, in the
conversations audio handler where you check ctx.agent, keep emit_one(ctx,
"conversations", operation, &audio) for agent=true, but for agent=false call
emit_one when OutputContext is non-human (i.e., when ctx.output_context
indicates JSON/machine output) and only call println!("{}", audio.audio_url)
when OutputContext is the human/default path; update the branch around
ctx.agent, operation, and audio to route JSON/non-human through emit_one instead
of printing a bare URL.

In `@src/commands/test_cases.rs`:
- Around line 165-174: The summary for bulk-create is being printed directly
with println! which bypasses the shared output layer; update the branch that
currently calls println! to emit the same summary through the existing helper
(emit_one) or only print when in human mode: replace the println! path with
emit_one(ctx, "test-cases", operation, &json!({ "created": created, "failed":
failed })) (matching the other branch) or wrap println! behind a check like if
ctx.human_mode() so machine-readable formats use the shared output; locate this
logic around the block that checks ctx.agent in the test-cases create handler.

---

Outside diff comments:
In `@src/cli.rs`:
- Around line 158-217: The fallback arm currently re-uses the moved value
cli.command causing a compile error; change the outer match to bind the arm
(e.g., use `command => { ... }`) and then perform the inner dispatch by matching
on that binding (match command { Commands::Agents { command } =>
commands::agents::execute(...).await, ... }). Ensure you reference the same
symbols (cli.command, Commands::Agents/Conversations/Runs/... and functions like
commands::agents::execute) and construct the CovalClient before the inner match
using the bound `command`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 119866e7-bfed-409f-bd4e-ba8e448ef601

📥 Commits

Reviewing files that changed from the base of the PR and between b15fcb7 and 322dfe2.

📒 Files selected for processing (23)
  • src/cli.rs
  • src/client/models/conversation.rs
  • src/client/models/simulation.rs
  • src/commands/agents.rs
  • src/commands/api_keys.rs
  • src/commands/auth.rs
  • src/commands/config.rs
  • src/commands/conversations.rs
  • src/commands/dashboards.rs
  • src/commands/metrics.rs
  • src/commands/mutations.rs
  • src/commands/personas.rs
  • src/commands/review_annotations.rs
  • src/commands/review_projects.rs
  • src/commands/run_templates.rs
  • src/commands/runs.rs
  • src/commands/scheduled_runs.rs
  • src/commands/simulations.rs
  • src/commands/test_cases.rs
  • src/commands/test_sets.rs
  • src/main.rs
  • src/output.rs
  • tests/cli_tests.rs

Comment thread src/commands/config.rs Outdated
Comment thread src/commands/conversations.rs
Comment thread src/commands/test_cases.rs Outdated
@lorenjphillips lorenjphillips force-pushed the feat/aci-1-agent-mode-foundation branch from 322dfe2 to 4ca561b Compare May 19, 2026 15:40
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/dashboards.rs`:
- Around line 229-230: The widget handlers are emitting the wrong resource name
("dashboards") when calling emit_list; update those calls to emit the correct
resource "widgets" instead. Locate the emit_list invocations in dashboards.rs
that pass the literal "dashboards" (the occurrences noted around the widget
handlers) and change the first argument to "widgets" so structured output labels
the results correctly; ensure all similar calls (the other occurrences
referenced) are updated consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2411302a-63e0-4a62-83d8-e1008816272b

📥 Commits

Reviewing files that changed from the base of the PR and between 322dfe2 and 4ca561b.

📒 Files selected for processing (24)
  • src/cli.rs
  • src/client/models/conversation.rs
  • src/client/models/simulation.rs
  • src/commands/agents.rs
  • src/commands/api_keys.rs
  • src/commands/auth.rs
  • src/commands/config.rs
  • src/commands/conversations.rs
  • src/commands/dashboards.rs
  • src/commands/metrics.rs
  • src/commands/mutations.rs
  • src/commands/personas.rs
  • src/commands/review_annotations.rs
  • src/commands/review_projects.rs
  • src/commands/run_templates.rs
  • src/commands/runs.rs
  • src/commands/scheduled_runs.rs
  • src/commands/simulations.rs
  • src/commands/test_cases.rs
  • src/commands/test_sets.rs
  • src/config.rs
  • src/main.rs
  • src/output.rs
  • tests/cli_tests.rs
🚧 Files skipped from review as they are similar to previous changes (21)
  • src/client/models/conversation.rs
  • src/client/models/simulation.rs
  • src/commands/personas.rs
  • src/commands/config.rs
  • src/commands/run_templates.rs
  • src/commands/scheduled_runs.rs
  • src/main.rs
  • src/commands/conversations.rs
  • src/commands/agents.rs
  • tests/cli_tests.rs
  • src/commands/review_projects.rs
  • src/commands/test_cases.rs
  • src/output.rs
  • src/commands/mutations.rs
  • src/commands/test_sets.rs
  • src/commands/api_keys.rs
  • src/commands/simulations.rs
  • src/commands/runs.rs
  • src/cli.rs
  • src/commands/auth.rs
  • src/commands/metrics.rs

Comment thread src/commands/dashboards.rs Outdated
@lorenjphillips lorenjphillips force-pushed the feat/aci-1-agent-mode-foundation branch from 4ca561b to 539ab7c Compare May 19, 2026 15:51
Copy link
Copy Markdown

@callumreid callumreid left a comment

Choose a reason for hiding this comment

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

looks like rust to me!

@lorenjphillips lorenjphillips force-pushed the feat/aci-1-agent-mode-foundation branch from 539ab7c to 68c64d9 Compare May 19, 2026 20:09
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli.rs (1)

158-217: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix the use-after-move error by binding the command in the fallback pattern.

The outer match cli.command consumes the enum value. The fallback arm using _ doesn't bind this value, so the inner match cli.command attempts to access an already-moved value. Change the fallback pattern to command => and use that binding in the inner match instead of re-accessing cli.command.

Suggested fix
     match cli.command {
         Commands::Login(args) => commands::auth::login(args, ctx).await,
         Commands::Whoami => {
             commands::auth::whoami(api_key.as_ref(), ctx);
             Ok(())
         }
         Commands::Config { command } => commands::config::execute(command, ctx),
-        _ => {
+        command => {
             let api_key = api_key.ok_or_else(|| {
                 anyhow::anyhow!(
                     "Not authenticated. Run `coval login` or set COVAL_API_KEY environment variable."
                 )
             })?;
             let client = CovalClient::new(api_key, api_url.as_deref());

-            match cli.command {
+            match command {
                 Commands::Agents { command } => {
                     commands::agents::execute(command, &client, ctx).await
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.rs` around lines 158 - 217, The outer match currently consumes
cli.command and then the fallback arm re-reads cli.command, causing a
use-after-move; change the fallback pattern from `_` to `command =>` to bind the
moved value and then use that binding in the inner match (e.g., match command {
Commands::Agents { command } => ..., Commands::Conversations { command } => ...,
etc.) instead of referencing cli.command again; update the inner match to switch
on the new local `command` binding so the enum value isn’t moved twice.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli.rs`:
- Line 123: Commands::resource() currently returns "dashboards" for the
Self::Dashboards variant even when the inner subcommand is the widget handler,
causing execute_widget() success strings ("widgets") to disagree with error
envelopes; update Commands::resource() to special-case the Dashboards variant by
matching its nested command (e.g., DashboardCommands::Widgets) and return
"widgets" in that case, otherwise keep returning "dashboards" for other
Dashboard subcommands so success and error payload resource names match.

---

Outside diff comments:
In `@src/cli.rs`:
- Around line 158-217: The outer match currently consumes cli.command and then
the fallback arm re-reads cli.command, causing a use-after-move; change the
fallback pattern from `_` to `command =>` to bind the moved value and then use
that binding in the inner match (e.g., match command { Commands::Agents {
command } => ..., Commands::Conversations { command } => ..., etc.) instead of
referencing cli.command again; update the inner match to switch on the new local
`command` binding so the enum value isn’t moved twice.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f8f7a7b-e074-4a18-b4a7-a0149e77671b

📥 Commits

Reviewing files that changed from the base of the PR and between 539ab7c and 68c64d9.

📒 Files selected for processing (24)
  • src/cli.rs
  • src/client/models/conversation.rs
  • src/client/models/simulation.rs
  • src/commands/agents.rs
  • src/commands/api_keys.rs
  • src/commands/auth.rs
  • src/commands/config.rs
  • src/commands/conversations.rs
  • src/commands/dashboards.rs
  • src/commands/metrics.rs
  • src/commands/mutations.rs
  • src/commands/personas.rs
  • src/commands/review_annotations.rs
  • src/commands/review_projects.rs
  • src/commands/run_templates.rs
  • src/commands/runs.rs
  • src/commands/scheduled_runs.rs
  • src/commands/simulations.rs
  • src/commands/test_cases.rs
  • src/commands/test_sets.rs
  • src/config.rs
  • src/main.rs
  • src/output.rs
  • tests/cli_tests.rs
🚧 Files skipped from review as they are similar to previous changes (20)
  • src/client/models/simulation.rs
  • src/client/models/conversation.rs
  • src/config.rs
  • src/commands/mutations.rs
  • src/commands/scheduled_runs.rs
  • src/commands/test_sets.rs
  • src/commands/run_templates.rs
  • src/main.rs
  • src/commands/agents.rs
  • src/commands/test_cases.rs
  • src/commands/review_projects.rs
  • src/commands/auth.rs
  • src/commands/personas.rs
  • src/commands/api_keys.rs
  • src/commands/conversations.rs
  • src/commands/config.rs
  • tests/cli_tests.rs
  • src/commands/runs.rs
  • src/commands/metrics.rs
  • src/output.rs

Comment thread src/cli.rs Outdated
@lorenjphillips lorenjphillips force-pushed the feat/aci-1-agent-mode-foundation branch from 68c64d9 to 68184a5 Compare May 19, 2026 20:20
@lorenjphillips lorenjphillips merged commit f794f4e into main May 19, 2026
7 checks passed
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.

2 participants