Skip to content

feat(agent): short replies, questions only when they matter - #12506

Merged
MarkusNeusinger merged 7 commits into
mainfrom
feat/agent-replies-short
Nov 1, 2026
Merged

MarkusNeusinger merged 7 commits into
mainfrom
feat/agent-replies-short

Conversation

@MarkusNeusinger

@MarkusNeusinger MarkusNeusinger commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Owner decision of 2026-11-01 after the user test: "Replies should be short and to the point, no waffle, and follow-up questions only when they really matter." root.md gets a "Replies" section. A reply leads with the result in two or three sentences, asks a question only when the turn cannot go on without the answer or the answer changes the data or the plot materially, closes with no invitation, and acts on the user's answer in the same turn. Brevity never drops a notice the data rules require: the loader's "reads", "keeps time" and "converts" lines, data changes with their counts, requested_not_found, binding warnings, tool errors and limits. The step-1 summary no longer asks a question by default and lists nothing the data does not have.
  • The "what changed" notes are now written in the language the user chats in and in plain words, without the code's own names such as load_user_data(), without what stayed as it was, and a fix lists only itself (adapter.md, and the self-review line in pipeline.py).
  • The prep eval measures every reply: words, questions, and whether it closes with a question. A closing question in a turn that was not blocked, or a reply over 100 words, is a reply miss, gated at 10 % (--max-reply-misses). A turn is blocked when the case is tagged ask_back, a data call failed, or a required role is left without a column. A case can send the page's summarize_data action, and four summary cases join the set.

Decided and documented here:

  • "Offer to ..." in root.md: the information stays, said as one short statement, through one rule in "Replies" rather than an edit of every passage. This keeps the diff away from the lines fix(agent): read the drawn texts of every library for the reply #12503 and fix(agent): a follow-up message keeps the plot's language #12504 change. Only needs_attention drops its "offer to fix one" outright: the user asks when they want a fix.
  • Notes language (lead, after fix(agent): a follow-up message keeps the plot's language #12504 merged): the reply language, since the notes explain the plot to the user. AdaptRequest.reply_language carries the session language only when it differs from the plot language, fenced as <reply_language> after the plot language. The adapter then writes changes in it and keeps the plot text in the plot language. Without the field the two languages are the same.
  • No server-side deduplication of notes: the repeats in the test were paraphrases from the self-review call, which no string comparison catches. The prompt and the self-review line now address the cause, and the final chat run showed no repeated note.
  • Length bound: 100 words, about the five sentences root.md allows at most.

Measurements

Claude Haiku 5.5 on Vertex AI eu, $0.985 in all.

Seven scripted chats rebuilt from the user test (A1, A3, B1, B4 and three summaries, two of them run twice), through /v1 in process with the deployed renderer, 20 replies per arm. The baseline arm ran on the origin/main prompts.

main this branch
Words per reply, mean (median, max) 58.7 (55, 80) 50.4 (52, 71)
Questions per reply 0.75 0
Replies that close with a question 13 of 20 0 of 20
Summaries: words, with a question 60.6, 11 of 11 55.1, 0 of 11
Plot replies: words 50.2 37.0
  • A3, "The scores are out of 100.": on main the root called no tool and answered "If you want the value axis to run from 0 to 100, tell me". The next plot kept the data's maximum. On this branch the root calls plot_pipeline with "Set the value axis to run from 0 to 100 ...", and the axis runs from 0 to 100.
  • B1 notes: on main they were English under a German reply and named loadUserData(). On this branch they are German, with no code names.

The prep eval (55 cases) ran with the same eval code on both arms:

main this branch
Exact operation match 53 of 55 55 of 55
Reply words, median (max) 69 (123) 57 (121)
Questions per reply 0.50 0.04
Replies that close with a question 20 of 52 2 of 52
Reply misses (gate 10 %) 21 of 52, FAIL 3 of 52, PASS

The main arm ran before the open-role rule joined the blocked definition, so its reply-miss count is not comparable one to one. The closing-question and question counts are.

Pre-PR Gemini 3.8 Flash check ($0.21, three findings):

  • Applied: a statement of what is possible may close a reply, and only an invitation may not. Applied: the policy test covers all seven loader prefixes.
  • Skipped: counting a question anywhere in the reply as a reply miss. The brief defines the check as a closing question, and the report still shows questions per reply.

Copilot review (one round, three findings):

  • Applied: the true median for an even number of replies, with a test. Applied: the golden adapter-prompt test is renamed for what it now pins.
  • Skipped, as a false positive: the "high" NameError on PrepCase's return annotation. The module has from __future__ import annotations and runs on Python 3.14, and its tests pass.

Plan

N/A (brief from the lead session; owner decision of 2026-11-01)

Test plan

  • ruff check ., ruff format --check ., mypy api core agents
  • pytest tests/unit tests/integration -n 6: 25,857 passed. New tests cover the "Replies" rules and every protected notice, the action-or-message case model, the reply measurement (closing question, long reply, and blocked by ask_back, by a failed call or by an open role), the reply gate, and a scripted summary run.
  • Live chat scenarios and the prep eval, as above.
  • After merging origin/main (feat(agent): the plot follows the site theme #12505, fix(agent): read the drawn texts of every library for the reply #12503, fix(agent): a follow-up message keeps the plot's language #12504) and the reply-language commit: ruff, mypy api core agents, and pytest tests/unit tests/integration -n 6 with 25,934 passed. Two flow tests through /v1 pin that a German plot under English replies gets its notes in English and the reverse, and that no <reply_language> block appears while the two languages agree.
  • Not measured live: the last notes rule ("never what stayed as it was") and the reply-language notes, since the $1 measurement budget was spent ($0.985).

Open points

  • The server's own loader lines ("R reads ...") stay English in the notes list, because the root reads them by their English prefixes.
  • The regression matrix does not read replies, so plot-turn replies were measured with a scratch driver only. Adding a reply measurement to matrix.py would collide with fix(agent): read the drawn texts of every library for the reply #12503.
  • Haiku still adds "confirm the data in the data panel" to about half of the summaries: one short sentence, left as it is.

https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8

MarkusNeusinger and others added 2 commits November 1, 2026 21:45
Owner decision of 2026-11-01 after the user test: replies are short and
to the point, and a follow-up question comes only when it really matters.

- root.md: a "Replies" section. The reply leads with the result in two or
  three sentences; the notices the data rules require stay (the loader's
  reads, keeps-time and converts lines, data changes with their counts,
  requested_not_found, refusals and limits). A question only when the
  turn cannot go on without it or the answer changes the data or the plot
  materially; no closing offer; an "offer" elsewhere in the instruction
  is said as a statement; a user's answer is acted on in the same turn.
  The step-1 summary no longer asks by default.
- adapter.md and the self-review line: the "what changed" notes are in
  the plot language, in plain words without the code's names, without
  what stayed the same, and a fix lists only itself.
- prep eval: every reply is measured (words, questions, a closing
  question); a closing question the turn was not blocked for, or a reply
  over 100 words, is a reply miss, gated at 10 %. A case can send the
  summarize_data action; four summary cases added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
- root.md: a statement of what is possible may close a reply; only an
  invitation may not (Gemini Flash pre-check).
- prep eval: a turn that leaves a required role of the spec without a
  column is blocked, the case root.md names for a question.
- test_policy covers all seven loader prefixes.
- changelog fragment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Copilot AI lite review requested due to automatic review settings November 1, 2026 21:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

An import-blocking NameError in PrepCase plus an incorrect even-count median calculation and stale golden-test documentation leave active correctness and maintainability issues.

3 open issues
What changed in this PR

This pull request makes agent replies shorter and more purposeful, asks follow-up questions only when necessary, and acts on user answers in the same turn. It also extends prep evaluation with summary actions, reply metrics, blocked-turn handling, and a reply-miss gate.

Changes:

  • Adds concise reply and meaningful-question guidance.
  • Updates adapter notes to use plain, plot-language descriptions without code-specific names.
  • Adds summary-action fixtures and reply behavior measurements.
  • Updates tests, documentation, README guidance, and the changelog.

This pull request requires changes.

File Description
tests/​unit/​agents/​runtime/​test_policy.py Tests the updated reply and adapter-note prompt rules.
tests/​unit/​agents/​evals/​test_prep.py Tests summary fixtures, reply measurement, blocked turns, and reply gating.
docs/​reference/​api.md Documents the updated summary behavior and current bindings.
docs/​concepts/​agent-network.md Describes concise replies, required notices, and current bindings.
changelog.d/​agent-replies-short.md Records the reply-policy and prep-evaluation changes.
agents/​README.md Documents prep evaluation cases, actions, and reply metrics.
agents/​evals/​prep.py Adds summary actions, reply measurement, blocked-turn detection, reporting, and gating.
agents/​evals/​fixtures/​prep/​en-summary-daily-readings.json Adds an English daily-readings summary case.
agents/​evals/​fixtures/​prep/​en-summary-ambiguous-dates.json Adds an English ambiguous-date summary case.
agents/​evals/​fixtures/​prep/​de-summary-haustiere.json Adds a German pet-count summary case.
agents/​evals/​fixtures/​prep/​de-summary-ausgaben-breit.json Adds a German wide-table summary case.
agents/​anyplot/​prompts/​root.md Defines concise replies, meaningful questions, protected notices, and same-turn handling.
agents/​anyplot/​prompts/​adapter.md Requires concise plot-language notes without code-specific wording.
agents/​anyplot/​pipeline.py Prevents self-review notes from repeating prior adapter changes.

Review effort: Lite

Comment thread agents/evals/prep.py Resolved
Comment thread agents/evals/prep.py Resolved
Copilot review of #12506: an even number of replies took the upper middle
value as the median; the pinned adapter-prompt test no longer claims the
prompt from before the split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
@codecov

codecov Bot commented Nov 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

MarkusNeusinger and others added 3 commits November 1, 2026 22:51
Brings in #12505 and #12503. The one conflict, the messages row of
docs/reference/api.md, takes main's `?theme=` wording and keeps this
branch's summary clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Brings in #12504; merged without conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The notes explain the plot to the user, so they belong in the language
the user chats in (lead, 2026-11-01). Since #12504 the plot language can
differ from the session language: AdaptRequest gains `reply_language`,
set only when the two differ and fenced as `<reply_language>` after the
plot language, and adapter.md writes `changes` in it while the plot text
stays in the plot language. Without the field the notes follow the plot
language, which then is the reply language too.

The design doc's two lines that said the notes stay English now say
which language they take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
@MarkusNeusinger
MarkusNeusinger merged commit 6fe243d into main Nov 1, 2026
16 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the feat/agent-replies-short branch November 1, 2026 22:41
@MarkusNeusinger MarkusNeusinger mentioned this pull request Nov 1, 2026
5 of 6 tasks
MarkusNeusinger added a commit that referenced this pull request Nov 1, 2026
## Summary
- Cuts v3.6.0 "All fifteen": `tools.changelog release` folds the 95
fragments of the window into `## [3.6.0] — 2026-11-01 — All fifteen`
(170 entries: Added 63, Changed 57, Removed 2, Fixed 42, Security 6),
repoints the compare links and bumps `pyproject.toml`, `uv.lock` and
`app/package.json` to 3.6.0.
- The window is 253 squash-merged PRs since v3.5.0 (#12160 to #12506).
The milestone: the agent chat ("Use with my data") serves all 15
libraries for admins, plus today's user-test fixes #12502 to #12506.
#12507 and the open sample-data, combo-request, date-convert and
data-grid work are not on main and not in this release.
- Hand-written aggregate lines: the `**Dependencies:**` bullet (14
Dependabot PRs; four security updates close 60 advisories, four critical
and 32 high) and the italic `*Catalog:*` line (no new implementations or
specs; 138 regenerations, 127 of them the MUI X 9 migration, 11
daily-regen improvements; 3 stored re-scores; coverage unchanged at 315
of 325 specs).

Routine decisions, documented here:
- Ten fragment bullets had no PR reference (`/pull_request` step 7 never
ran for #12404, #12408, #12409, #12235, #12458, #12461, #12462). Before
the cut, their real numbers were appended (found with `git log
--diff-filter=A` on each fragment), and the two Jules fragments (#12408,
#12409) were wrapped to the file's line width without changing their
wording.
- Completeness: every non-bot commit in `v3.5.0..origin/main` has an
entry except #12469, #12471 and #12472 (`skip-changelog`; a temporary
DEBUG diagnosis and its revert, net zero) and #12298 (`plots/`-only
metadata attribution, exempt). No fragment was added.
- Catalog count: implementation-file changes in the window, classified
by whether the file existed at `v3.5.0` (all did, so no new
implementations), cross-checked against the merged pipeline PRs' labels
on GitHub (127 `regen:forced`, 11 `regen:improved`, 3
`review-writeback`); the MUI X split comes from `library_version` and
`generated_by` in `plots/*/metadata/javascript/muix.yaml`. Advisory
counts are unique GHSA ids of the Dependabot alerts fixed after the
v3.5.0 tag; the nanoid and `@vitest/mocker` alerts closed by #12235 and
#12249 belong to those PRs' own entries and are not counted.

## Plan
`agentic/commands/release.md`, steps 1 to 5. The tag and the GitHub
release (steps 6 and 7) follow after the merge.

## Test plan
- [x] `uv run python -m tools.changelog release 3.6.0 --title "All
fifteen" --dry-run`, then the real run
- [x] The diff touches only `CHANGELOG.md`, `pyproject.toml`, `uv.lock`,
`app/package.json` and the 95 deleted fragments
- [x] `uv lock --check` passes; the lock changes only the project's own
version line
- [x] `uv run pytest tests/unit/test_version_sync.py` passes (2 tests)
- [x] `uv run python -m tools.changelog check --base origin/main` passes
- [ ] CI green

https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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