Repository navigation
feat(output)!: replace agent mode with a default JSON envelope for everyone - #899
Conversation
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
When reference-table discovery fails (e.g. HTTP 429), the code incorrectly emits a continuation offset into an incomplete result set, causing callers that resume from that offset to skip entries. The fix is to suppress next_offset whenever the discovery is incomplete so callers retry the original page.
🤖 Bits Code Review · Commit b4284b1
There was a problem hiding this comment.
Entity queries filtered with --jq '.results' lose all continuation information, allowing partial inventories to appear complete. The existing cursor-hint helper can preserve that information on stderr.
🤖 Bits Code Review · Commit 68b7a73
33fb704 to
a5cb724
Compare
0683116 to
3911b3c
Compare
There was a problem hiding this comment.
Two production-impacting bugs were identified: envelope re-rendering silently mutates payload structure by double-promoting the data key through unwrap_envelope/build_envelope, and runbook polling evaluates enveloped subprocess output directly against conditions (instead of unwrapping first), causing all poll conditions to time out rather than resolve. A third issue affects the test harness SLO ID lookup paths, which must be updated to account for the new data.data nesting introduced by collision wrapping.
🤖 Bits Code Review · Commit 3911b3c
0fcc8b7 to
287ec02
Compare
…eryone
Agent mode changed pup's behavior when an AI agent was detected: JSON was
wrapped in {status, data, metadata} (hoisting `data` and dropping `meta`,
`links`, and `included`, which lost pagination cursors), --help returned
a JSON schema, and prompts were auto-approved. Humans and agents saw
different shapes. Now every caller gets the same output; agent detection
only adds the User-Agent token and auto-approves CLI prompts.
- Remove agent mode: no FORCE_AGENT_MODE / PUP_AGENT_MODE, --help is always
text. Agent detection adds `ai-agent <name>` to the User-Agent and still
auto-approves CLI prompts (also in the extension pre-parse path, so
extension children get PUP_AUTO_APPROVE). The legacy --no-agent opts out
of that auto-approval, as it did before. --agent is a hidden no-op.
- Wrap JSON and YAML output in {status, data, metadata} by default. A body
with a top-level `data` keeps it and its siblings (meta, links, included);
any other body (or one with its own status/metadata key), and any --jq
result, becomes `data`.
- Table, CSV, and TSV print only rows on stdout. CSV/TSV now honor the
command's row/column hints like the table (e.g. monitors search prints
one row per monitor), and render a --jq result as-is.
- Built-in metadata: count, truncated, command, next_cursor, next_offset,
next_page, next_action, warnings. Continuation is lifted from the
response (cursors, totals, and page/page_count pagination such as
monitors search's own `metadata`), survives --jq, and next_action names
each command's own flag (read from clap, e.g. --page-cursor, --page).
- For table, CSV, and TSV, always print the metadata to stderr as one
block: `metadata: key=value ...`, then `next_action:` and `warning:`
lines. With --no-envelope only `warning:` lines are printed.
- Add --no-envelope (env PUP_NO_ENVELOPE, inherited by extensions) to print
the raw response body; the legacy --no-agent also prints it raw.
- pup format: --count/--command/--next-action populate metadata. A piped
pup envelope prints as-is for JSON/YAML (lossless: `format | format` is
idempotent); rows and --jq use its payload. Look-alike documents are
left whole.
- Runbooks: pup steps run the child with --output json --no-envelope, so
`until` conditions and `capture:` see the raw payload; the parent renders
that payload for display in the user's format and envelope choice
(metadata on stderr for table/CSV/TSV). Plain-text output passes through.
- ddsql schema: list in `data`, next_offset/warnings in metadata; no
next_offset after a rate-limited lookup; reference tables sorted by name.
- pup agent schema: envelope contract in full, compact, and filtered
schemas; --no-envelope listed in global_flags.
- traces get / get --pruned validate the response shape.
- Test harness: id_from paths audited against each list response's shape
(merged vs wrapped whole, e.g. slos list -> data.data, events list ->
data.events); a stale path is a defect, not a silent skip. Snapshot
baselines updated for both modes.
BREAKING CHANGE: JSON and YAML output are wrapped in {status, data,
metadata} for every caller, including --jq results (`$(pup ... --jq '.id')`
now yields an envelope). CSV/TSV follow each command's row and column
selection, and table/CSV/TSV print a metadata block on stderr. Pass --no-envelope or set PUP_NO_ENVELOPE=1 for the raw body.
--help no longer returns JSON, and FORCE_AGENT_MODE / PUP_AGENT_MODE are
ignored.
The openapi-transformer generator must emit the new formatter contract
(src/formatter.rs: format_and_print, format_and_print_with_table,
format_and_print_with, output, output_with_table, output_with_meta, and the
Metadata re-export; src/client.rs test fixtures without agent_mode;
src/config.rs stays a re-export of crate::settings, which holds Config).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
287ec02 to
384a030
Compare
PR #908 regenerated src/generated/downtime.rs against the pre-#899 formatter API (agent_mode arg, four-field Metadata). Both merged to main back to back, leaving main and this release branch unable to compile. - Build Metadata with ..Default::default() for the new pagination fields - Print via formatter::output_with_meta instead of the removed signature Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Breaking change. Removes agent mode. Output and help are now identical for humans and AI agents, and the
{status, data, metadata}JSON envelope is the default output for everyone. Agent detection now drives two things only: theai-agent <name>User-Agent token, and auto-approving CLI prompts (unchanged frommain).Breaking changes
main, humans got the raw body and only detected agents got the (JSON-only) envelope.datakeeps it in place. Its sibling keys (meta,links,included) are now kept; the old envelope dropped them, losing cursors.statusandmetadataare added alongside.data. Example:monitors list | jq '.[]'becomesjq '.data[]'.statusormetadatakey is wrapped whole. For example,monitors searchkeeps its API pagination atdata.metadata.monitors search -o csvprints one row per monitor (it used to print the whole{counts, metadata, monitors}body as a single row). Table, CSV and TSV always print the metadata to stderr as one block:metadata:line is always printed, with keys in a fixed order (command,count,truncated,next_cursor,next_offset,next_page), absent keys omitted and strings JSON-quoted. With--no-envelopeonlywarning:lines are printed. CSV/TSV changed for commands with hints:monitors list/search,logs search,slos list,users list,incidents list,dashboards list, andpup format --rows-at/--row-at/--columns. Commands with explicit columns now print only those columns.--jqwith table/CSV/TSV renders the jq result as-is.--jqresults are wrapped too. Onmain, humans got the bare filter result; now it becomes the envelope'sdata. Soid=$(pup monitors get 1 --jq '.id')yields an envelope instead of1. Use--no-envelope(orPUP_NO_ENVELOPE=1) to get the bare result.pup apioutput is enveloped, including with-i: the response headers are printed first and describe the raw body, then the envelope follows.metadata.noteis gone.metadata.truncatedis always present.next_cursor,next_offset,next_page,warnings.--jq,countdescribes the filtereddata;truncatedandnext_cursorare kept.--no-envelope(orPUP_NO_ENVELOPE=1) prints the raw response. The legacy--no-agentalso prints the raw response, so scripts written to the old guidance keep working, and, as onmain, it opts out of agent auto-approve.--no-envelopedoes not affect auto-approve.--helpno longer returns a JSON schema for agents; usepup agent schema.FORCE_AGENT_MODEandPUP_AGENT_MODEare no longer read or set.--agentis a hidden no-op.ddsql schema tables/columns:datais the list;next_offsetandwarningsare inmetadata. Reference-table discovery now requestssort=table_name, so offsets are stable across pages.Changes
src/output.rs:Metadata(nowDeserialize) andbuild_envelope.command(the running command path),next_cursorandtruncated. Commands only set what the response can't say.meta.page.after,meta.page.next_cursor,meta.after,meta.next_cursor,meta.pagination.next_page(cicd flaky tests),page.next_cursor,data.next_cursor(llm-obs MCP endpoints),next_cursor,next_page_token(llm-obs patterns). Only fields that always mean "next page" are used; deployment gates'meta.page.cursoris the current page and is ignored.meta.page.total_filtered_countexceeding the returned items (security findings), and from page pagination:metadata.page + 1 < metadata.page_countgivesnext_page(monitors search).truncated: falsemeans "not known to be truncated": some APIs (e.g. incidents search) don't report it reliably.next_actionnames the command's own cursor or page flag, read from its clap definition (--cursor,--page-cursor,--page-token,--page); commands without one say so.stderr_block) replaces the earlier ad-hoc stderr hints. Resource-diff hints print separately only when there are no changes or with--no-envelope.src/formatter.rs: re-exportsMetadataand addsformat_and_print_withandoutput_with_meta. There is noagent_modeparameter.useragent::agent_auto_approves) is applied inmain_innerand in the extension pre-parse path (PreParsedGlobals::apply_to), so extension children still getPUP_AUTO_APPROVE. Confirm-prompt behavior is otherwise unchanged frommain.--no-envelopeis forwarded to extensions viaPUP_NO_ENVELOPE.--jq '.results'finding);traces getspan count, resource-diff next step.pup format:--count,--commandand--next-actionpopulate metadata again. Piped pup envelopes print as-is for JSON/YAML (lossless, soformat | formatis idempotent), while rows and--jquse their payload; a look-alike{status, data, metadata}document is only unwrapped if its metadata has a booleantruncatedand only pup's own keys, otherwise it is left whole.pup agent schema:envelopecontract in the full, compact and filtered schemas, with--no-envelopeinglobal_flags.returns.datadescribes envelopedata, and the conformance test runs bodies through the real envelope builder.--output json --no-envelope, sountilconditions andcapture:see the raw payload. The parent renders that payload for display in the user's format and envelope choice (metadata on stderr for table/CSV/TSV), using generic row selection; plain-text output passes through unchanged. No temp files or path env vars are involved.CLAUDECODE=1, and command discovery usespup agent schema. Both modes' baselines are the envelope shape.id_frompaths were audited against each list response's shape (merged vs wrapped whole, e.g.slos list→data.data,events list→data.events;incidents listandsynthetics suites listwere already wrong onmain), and a path that doesn't match the output is reported as a defect instead of a silent skip.Notes for reviewers
src/config.rs,src/client.rsandsrc/formatter.rscarry the openapi-transformer "generated" header.Confignow lives in the hand-maintainedsrc/settings.rs(since refactor(config): split generator-owned contract from settings #913), andsrc/config.rsonly re-exports it. The generator must emit the same contract: noagent_modeinsrc/client.rstest fixtures,format_and_print(data, format, jq),format_and_print_with_table(data, format, jq, table),format_and_print_with(data, format, jq, meta, table),output,output_with_table,output_with_meta, and theMetadatare-export.agent schema,auth status, runbooks and bits print their own JSON and are not enveloped (unchanged frommain).Testing
cargo test -- --test-threads=1: 2,211 unit tests plus all integration tests pass.cargo clippy --all-targets -- -D warningsandcargo fmt --checkpass, and thewasm32-wasip2build is clean.--jqkeeping metadata;--no-envelope/--no-agent/PUP_NO_ENVELOPE;CLAUDECODE=1auto-approves, no marker does not,--no-agentopts out,--no-envelopedoes not, and the extension pre-parse path matches;next_offset;tests/idp_query_cli.rs);tests/pagination_cli.rs), including a command whose cursor flag is--page-token;tests/runbook_cli.rs): polluntilon a raw--jqresult, a timeout negative, and capture of the raw payload, table display, a plain-text step, andPUP_PAYLOAD_FILEnot being honored;tests/output_formats_cli.rs):pup formatidempotence for four payload shapes, one CSV/TSV row per monitor with parseable stdout and metadata on stderr, the table, the YAML envelope, raw--no-envelopeYAML/CSV,--jqwith CSV, an empty result, and a bare v1 array;next_action, warnings), new cursor/truncation locations, look-alike envelopes left whole, and per-command cursor flags.🤖 Generated with Claude Code