Repository navigation
feat(agents): keep the notes pasted around a table for the assistant - #12168
Merged
Merged
Conversation
The parser's table finder (#12166) left out a title, an export's metadata, a source line or a note around a pasted table, and those lines vanished. They are the user's words about the data, so they are now kept as ParsedDataset.context_lines: one cleaned text line per left-out record, joined with the delimiter's display form, summary rows excluded, at most MAX_MESSAGE_CHARS (2,000, now a shared constant in schemas.py) in all. The dataset route checks them before anything stores them: the cells' INSTRUCTION_LIKE pre-filter, then, after the dataset judge passed the table, one scope-judge call on the lines fenced as <user_notes>. A hit, a refusing verdict or no verdict drops only the notes with a warning; the table is stored and no strike is counted. The judge's tokens are booked to the user; the notes_judge attribution line holds verdict and counts. The stored dataset keeps the judged notes; push_version carries them to every prepare/generate/to_long version, a new paste replaces them. The summarize_data turn appends them after a preamble (policy.user_notes_block), get_dataset_profile returns them as user_notes (its cap grows by 9 KiB so the profile keeps its own 16 KiB), and the session block names that notes exist with a fixed line. root.md tells the root to use them as context and act on a clear request at once, keeping "confirm the data first"; the scope rubric covers notes, with seven new scope-eval cases (tag notes). The dataset answers (route, undo, to_long, done.dataset) carry context_lines, and the data panel shows them collapsed as "# notes sent to the assistant". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Attribution character counts and documentation contracts remain inconsistent with the implemented behavior.
4 open findings
What changed in this PR
Adds persistent, screened notes from lines surrounding pasted tables, exposing them to the assistant and data panel.
Changes:
- Extracts, screens, stores, and versions pasted notes.
- Delivers fenced notes through summaries and dataset-profile tools.
- Adds frontend display, documentation, eval cases, and tests.
| File | Description |
|---|---|
agents/anyplot/data/parse.py |
Extracts and caps context lines. |
agents/anyplot/data/store.py |
Stores notes and accounts for size. |
agents/anyplot/dataset_versions.py |
Carries notes across versions. |
agents/anyplot/opening.py |
Adds note screening helpers. |
agents/main.py |
Screens and delivers notes. |
agents/anyplot/policy.py |
Adds fenced note blocks. |
agents/anyplot/tools/session.py |
Returns notes with dataset profiles. |
agents/anyplot/plugins/tool_safety.py |
Extends profile result limits. |
agents/anyplot/agent.py |
Announces stored notes in session context. |
agents/anyplot/schemas.py |
Centralizes the message limit. |
agents/anyplot/prompts/root.md |
Directs assistant note handling. |
agents/anyplot/prompts/scope_judge.md |
Defines note-scope verdicts. |
agents/stream.py |
Documents notes in done events. |
agents/evals/scope.py |
Evaluates fenced note inputs. |
agents/evals/scope.evalset.json |
Adds seven note cases. |
agents/evals/prep.py |
Prices note-judge calls. |
agents/evals/matrix.py |
Accounts for note-judge usage. |
api/routers/agent.py |
Documents proxied response fields. |
app/src/lib/agent.ts |
Adds note fields to client contracts. |
app/src/lib/agent.test.ts |
Tests note-event parsing. |
app/src/sections/agent-chat/DataPanel.tsx |
Displays collapsible notes. |
app/src/sections/agent-chat/DataPanel.test.tsx |
Tests note display behavior. |
tests/unit/agents/data/test_parse.py |
Tests extraction, caps, and warnings. |
tests/unit/agents/data/test_store.py |
Tests note persistence and accounting. |
tests/unit/agents/runtime/test_dataset_versions.py |
Tests version propagation and replacement. |
tests/unit/agents/runtime/test_plugins.py |
Tests profile-result sizing. |
tests/unit/agents/runtime/test_service_flow.py |
Tests screening and end-to-end delivery. |
tests/unit/agents/runtime/test_stream.py |
Verifies maximum done-event size. |
tests/unit/agents/evals/test_scope.py |
Tests note-specific eval routing. |
docs/reference/api.md |
Documents API note behavior. |
docs/concepts/agent-network.md |
Explains the architecture and lifecycle. |
agents/README.md |
Updates agent feature and eval documentation. |
changelog.d/paste-context-lines.md |
Records the user-facing feature. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…message cap Copilot review of #12168: the notes_judge attribution counts characters as the cap and the judge input do (with the line breaks), a test pins the evals' own MAX_MESSAGE_CHARS to schemas.MAX_MESSAGE_CHARS (importing agents.anyplot in the evals would build the agents), and the design doc's done.dataset shape names context_lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Copilot review of #12168: store_dataset kept any parsed context_lines, so the development fixture seed (and an eval ingest) could store notes that never passed the pre-filter and the scope judge. store_dataset now drops them unless the caller passes notes_screened=True, which only the dataset route does after _screen_notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
3 of 4 tasks
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 14, 2026
… pasted data (#12171) ## Summary Two fixes from the owner's live test of 2026-10-14. - **No false refusal right after a refusal.** After "Wie wird das Wetter morgen in Wien?" was refused (`needs_data`), the next message, "Welcher Tag im April war der wärmste, und kannst du ihn im Plot markieren?", was also refused. - Cause: `judge_context` passed the fixed refusal (the before_run halt event, author `model`) to the judge as `<last_assistant_turn>`. - Code fix: `policy.fixed_replies()` and `scope_guard.is_service_text()` skip every service-written refusal, in every language. The judge sees the last reply a model wrote, or none. - Rubric fix: a question about the user's own data (warmest day, maximum, mark the highest value) is `in_scope`. A weather word, date or place alone never makes a message `needs_data`. - Both fixes are needed. In a scratch repro on the real judge (3 runs each), the code fix alone left in-073 at `needs_data` 3/3, and both together gave `in_scope` 3/3 for all three cases. - **The session language comes from the pasted data.** This needs no new model call: - The notes judge (#12168) and the dataset judge (column names) already return `lang`. - The language is taken from the notes first, then the headers. `en` counts as no signal. - A paste overwrites only `default` or an earlier `pasted` language, never `detected` or `chosen`. - The dataset route returns `language: {language, source}`. The page syncs its chip (`lang: de · from data`) and shows one notice. ## Measurements - The full scope eval on the real judge (Claude Haiku 5.5, `eu`) passed: 203 cases, adversarial recall 100 %, false refusals 0 %, exact 99.0 %. - The new cases in-071 to in-077 (`data_question`, three of them `after_refusal`, German and English) and adv-066 are all right. - 1 miss, off-053: the same miss as on main, and the open data-from-code PR reclassifies it as in scope. ## Plan N/A ## Test plan - [x] `pytest tests/unit tests/integration`: 10,094 passed. ruff, ruff format, mypy and the changelog check pass. - [x] Frontend: lint, Prettier, type-check, 1,015 Vitest tests and build pass. - [x] Scope eval on the real judge, as above. - [ ] After merge: the live sequence (a refusal, then a data question) and a German paste whose first summary is in German. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2 of 3 tasks
MarkusNeusinger
added a commit
that referenced
this pull request
Oct 14, 2026
## Summary - **JSON-stat 2.0 from a statistics office's API now parses.** The owner's test of 2026-10-14 used the Swiss Federal Statistical Office (BFS) PxWeb API, which answers `json-stat2`, and the parser refused it as `unparseable`. Eurostat, Statistik Austria, Statistics Norway and others use the same format. The parser reads it deterministically (no model) into one flat long table: - one column per dimension with more than one category, named by the dimension label, with category labels as cells; - a `value` column; - the rows in the dataset's own row-major order. - **Single-category dimensions become notes.** They leave the columns (with one warning) and become `<label>: <category>` notes in `context_lines`, after the dataset's `label`, `source` and `note`. That way they pass the paste screen (pre-filter + scope judge, #12168) before a model reads them. - **Strict validation before a row is built:** - `id` and `size` must match, and the size product must equal the number of values. - Category indexes may be an array or an object; a sparse `value` object is supported. - Missing values stay empty, and `status` symbols are ignored with one warning. - `MAX_ROWS` and `MAX_COLUMNS` apply. - Every inconsistency is `unparseable` with a message that names no data. - **Formats.** A JSON-stat 1.x bundle with one dataset is read. Several datasets or a 2.0 collection are refused with a clear message. The new `source_format` is `json_stat`. ## Measurement The real BFS sample (births and deaths 2024 by canton, 2 KB) becomes 52 rows of `Kanton`, `Demografische Komponente` and `value`. The notes name the year, the nationality total, the sex total and the source. ## Plan N/A ## Test plan - [x] 14 new test functions in `TestJsonStat` (23 cases): the trimmed BFS sample, sparse values, index arrays, missing labels, constant dimensions, duplicate labels, a 1.x bundle, 10 inconsistent documents, row caps, unchanged non-JSON-stat shapes, the depth rule, and a loader round trip. - [x] `pytest tests/unit tests/integration`: 10,169 passed. ruff, ruff format, mypy and the changelog check pass. - [ ] After merge: paste the BFS JSON-stat answer into the live chat (owner's test round). --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
context_lines. Summary rows are data and not part of them. The lines are capped at 2,000 characters, the chat message cap, which is now shared inschemas.MAX_MESSAGE_CHARS.INSTRUCTION_LIKEpre-filter on the lines, then one scope-judge call with the lines fenced as<user_notes>, after the dataset judge passed the table. Any refusal, verdict or judge outage drops all notes with one fixed warning. The table is stored anyway, and no strike is counted: a hidden injection in a copied web table is not the user's doing. The judge's tokens are booked like the dataset judge's. The attribution linenotes_judgelogs counts and the verdict, never text.summarize_dataturn, fenced;get_dataset_profileunderuser_notes, so they surviveContextFilter(6);push_version(prepare, generate, to_long), replaced by a new paste, and restored by undo.root.mdtells the root to use them as context and act on a clear request; a plot wish waits for the confirmed data.scope_judge.mdhas a section on notes.# notes sent to the assistant · N lines.Second opinion and deviation
Fable reviewed the design before the build. Two of its points shaped this PR:
summarize_dataturn (409 run_active), and the notes would leave the history after six turns.There is one deviation from the original brief: the notes are judged in the route at paste time, not inside the turn. Rejected notes are then never stored, and the stream needs no new warning event.
Measurements
The full scope eval ran on the real judge (Claude Haiku 5.5,
eu) and passed. It has 195 cases (188 before, plus 7 newnotescases).in_scope(a source line with a URL, copyright, footnotes, units, a station name, a plot wish) and the 1 hidden attack refused.off-053("Create a matplotlib plot of a sine wave for my homework" →in_scope). It misses the same way on main, so it is not a regression. With the planned data-from-code feature it may become in scope.Plan
N/A
Test plan
pytest tests/unit tests/integration: 10,033 passed. ruff, ruff format, mypy and the changelog check pass.