Skip to content

feat(agents): generate data from code in the sandbox (run_data_code) - #12172

Merged
MarkusNeusinger merged 18 commits into
mainfrom
feat/run-data-code
Oct 14, 2026
Merged

MarkusNeusinger merged 18 commits into
mainfrom
feat/run-data-code

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

  • Data from code, offline (owner decision of 2026-10-14). A user can paste or type a snippet that generates the data (Python, JavaScript, R, a formula from a maths or physics textbook), or name a computation in words ("draw the Lorenz attractor"). The root translates it into Python, and the new root tool run_data_code(code, source_language) runs it in the renderer sandbox. The resulting table becomes the next dataset version, which the user confirms.
  • Which tool computes the data depends on what the computation needs. A closed formula over one or two ranges stays on the safe formula language (generate_data). Numerical integration (ODEs), iteration, random numbers, cumulative sums or several dependent steps go to run_data_code.
  • Layers, each designed and reviewed by a Fable second opinion before and after the build:
    • Validator data profile (validate_data_code):
    • Harness data mode (harness.py --data):
      • Imports everything first, then arms an audit hook that refuses every open (read too), every new import, directory probes, ctypes, subprocess, sockets, exec/fork, marshal and pickle, and every route to a frame.
      • Catches everything, SystemExit included.
      • Takes df, resets a non-default index, refuses MultiIndex columns, and writes out.csv under the shared caps.
    • Renderer route: an additive POST /run-data with its own wire models (extra="forbid"), the same caller check, slot, watchdog and kill path, and source ≤ 16 KiB, timeout ≤ 30 s. An old renderer answers 404, which the tool maps to unavailable. The deploy smoke runs a 2-row snippet.
    • No raw snippet output reaches the model. Errors are a fixed vocabulary (exception class and line only); stdout is discarded.
    • One ingestion guard for pastes and code tables. dataset_guard.check_dataset (pre-filter + dataset judge) runs on both, so cells generated by code are judged too.
    • Rate and budget. One rated start per turn through ledger.rate_start / RunQueue.rate; the plot pipeline will use the same helper when the queue becomes pipeline-only. Also 30 data runs per user per day (AGENT_DAILY_DATA_RUNS), at most 2 calls per turn, and a 6,000-character code cap.
    • Dataset route. A classifier runs before parsing and answers 422 code or 422 formula. The page offers .send() to the chat for code within 2,000 characters.
    • Scope rubric:
      • Data code is in scope.
      • A general-purpose script is out of scope.
      • Code that touches files, the network or the system is an attack.
      • off-053 (a sine wave "for my homework") is now in_scope under the formula path.
    • UI. The data panel shows the Python that made a version in a collapsed # code that made this data block.

Deploy order

The renderer should deploy first (/run-data), then the agents service. Either order is safe: against an old renderer, the tool answers unavailable and runs nothing.

Measurements

  • Scope eval on the real judge (Claude Haiku 5.5, eu, the combined state after merging main): passed. 219 cases, adversarial recall 100 %, false refusals 0 %, exact 99.1 %, and 4 cases unavailable (the judge's answer failed its schema twice).
  • Prep eval for tool choice on the real model: the three cases described in words (Lorenz ODE, logistic map, random walk) chose run_data_code, and the four formula cases chose generate_data. The fake renderer's fixture table made the root re-run, so table sizes are checked only with a real renderer, after deploy.

Plan

docs/concepts/agent-network.md (data tools, renderer API, threat table) and agents/README.md.

Test plan

  • pytest tests/unit tests/integration: 10,464 passed. ruff, ruff format, mypy and the changelog check pass.
  • Frontend: lint, Prettier, type-check, 1,020 Vitest tests and build pass.
  • Harness data mode was run in subprocesses against the escapes from both Fable reviews; each one fails the run. pandas, numpy and scipy work under the guard.
  • Scope eval and the routing prep cases, as above.
  • After deploy: the renderer's /run-data smoke, then the live test round (Swiss BFS data, a JS snippet, Lorenz in words, an attempt at a general-purpose script).
  • Not verified: the audit hook in plot mode (it is armed in data mode only, because there is no local Docker for a catalogue render sweep), and the data panel in a browser on both viewports and themes. The live test round covers the panel.

MarkusNeusinger and others added 14 commits October 14, 2026 12:29
validate_data_code runs every SECURITY check except savefig on a snippet the
root translated to Python: the import surface is COMMON plus the standard
random module and the scipy submodules (no plotting stack, sklearn or
statsmodels), every private attribute is banned on any receiver, the table
must be assigned to df at the top level, and literal-data counts dicts as
containers plus a file-wide cap of 40 numeric literals. File, URL and network
findings carry one fixed message ("paste the file's contents in the data
panel instead") and no message mentions load_user_data(). code/data_code.py
strips a top-level `if __name__ == "__main__":` guard before validation.

A string-dispatch finding no longer echoes a literal matched only by the
read_*/fetch_*/load* pattern; random.main joins MODULE_ESCAPES.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
`harness.py --data snippet.py` runs a data-generating snippet instead of a
plot: it imports numpy and pandas (which load ctypes), opens out.csv, then arms
DataGuard with sys.addaudithook, which refuses ctypes.*, os.system, os.exec*,
os.posix_spawn, os.spawn*, os.fork*, subprocess.*, socket.*, filesystem
changes and every open for writing. It takes df from the run's globals
(DataFrame only, no MultiIndex columns, a non-default index reset into
columns, names as text, the parser's row, column and byte caps) and writes the
CSV through the handle it opened before the guard. The snippet never chooses
the exit code: each refusal has its own (DATA_EXIT), and a refusal the snippet
caught still refuses the run. Plot mode is unchanged: the guard is not armed
there until a render sweep of the catalogue under it has been run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
An additive route with its own closed models: DataRunRequest {job_id, source,
timeout_s <= 30} and DataRunResponse {exit_code, timed_out, reason, measured,
limit, csv_text, stderr_tail, wall_s, attempts, timings, version,
mem_available_mb}. The executor runs the snippet as snippet.py with the
harness in data mode, no theme and no matplotlib environment, through the
same launch, watchdog, kill path and launcher retry as a theme, and collects
only out.csv after exit code 0 (read_regular under MAX_CSV_BYTES, strict
UTF-8, else output_rejected). Data runs take the one render slot and share
the idempotency rules (key: job_id and "data"); the cancel route stops them
too. MAX_CSV_BYTES is pinned to the parser's cap in test_wire.py. The deploy
smoke posts a 2-row snippet to /run-data on the candidate revision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
The root's eighth session tool: run_data_code(code, source_language) strips
a __main__ guard, validates the snippet with the DATA profile, checks the
per-user daily count (AGENT_DAILY_DATA_RUNS, 30) and books the turn's start
in the run queue's rate window when the turn was submitted unrated
(RunQueue.rate through RequestLedger.rate_start), runs it through
services.backend.run_data in one render slot, parses out.csv like a paste,
requires a number or date column, runs the pre-filter and the dataset judge
(now one function in dataset_guard.py that the dataset route calls too), and
stores the table as the next dataset version (version 1 without data) with
the Python that ran kept on the version (StoredDataset.code, the dataset
payload's `code`). Refusals carry only server-written lines: validator
findings fenced as tool_notes, an exception as class and line of snippet.py
(gates.error_summary with code_file), fixed lines for the harness's exit
codes; nothing the snippet printed reaches the model.

RenderBackend gains run_data: fake (fixture CSV or a script), local (Docker,
harness in data mode), remote (POST /run-data with the same token, retries
and cancel; 404 is DataRunUnsupported, answered `unavailable`), sandbox
(unsupported), and SerialRenderer.run_data takes a slot of the run lane.
ToolSafety: _DataCodeArgs (code <= 6,000 characters, a fixed language set),
`code` exempt from the link rule, at most two calls per invocation, the data
version result keys and a 16 KB cap. AGENT_DATA_CODE switches the tool off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
data/classify.py decides what a paste is before any parser or model reads it:
code (an import, a definition, an R assignment, a loop header, a call of a
data module, an arrow function, with code-shaped lines at least half of the
text), a formula (generate.looks_like_formula, within the chat's 2,000
characters) or neither. `np.linspace(0, 10, 100)` no longer parses as a
one-row table. Code answers 422 code at any length, a formula 422 formula as
before; a confirmed transcription is never classified. The attribution line
keeps its outcome (`code` or `formula`), never the text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
root.md: the run_data_code tool and a "Code" section under step 1: translate
any language or a textbook formula to Python with numpy, pandas, random and
math (scipy.stats when needed), assign df, keep only the data part (no
plotting, file, network or system calls; another plot type is a later
phase), seeds give the same distribution, not the same values, never write
values from memory, fix and retry once after a refusal, never show the code
in a reply. The "never write code in a reply" rule stays; run_data_code is
the only code path for data.

scope_judge.md: a snippet or formula whose job is to generate the plot's
data is in_scope in any language; code with another purpose is
out_of_scope; code that touches files, the network or the system is attack;
two examples (the cap of the example table goes to 15).

Evals: 14 scope cases (code-001..014: Python, JavaScript, R, a textbook
formula; general runners; system access) and off-053 ("a sine wave for my
homework") moves to in_scope: drawing a curve on the plot page is the
formula path. Eight prep cases score run_data_code by the size of the table
it stored (a random walk, a NumPy sample, a JavaScript loop, R rnorm/seq, a
logistic map, a snippet with plt calls to strip) or by nothing stored
(`refuse`: a file read, a general-purpose script).

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

The BFF passes `422 code` through (`_UPSTREAM_CODES`). The page keeps code the
dataset route refused like a refused formula (`formulaKind`), and the line
under the box offers `.send()`, which sends it to the chat where the
assistant runs it; code longer than a chat message gets a line that asks to
shorten it instead of `.send()`. A data version computed from code carries
its Python (`done.dataset.code`), which the data panel shows in a
`# code that made this data` block, collapsed behind `.show()`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
agent-network.md: the data-code flow under step 1, the renderer's
POST /run-data and the harness's data mode with its audit hook under Render,
the root's eighth tool, ToolSafety's two-call cap and link-rule exemption,
the /v1 table (422 code, done.dataset.code), and six threat rows (the
sandbox used for other purposes, native code from a snippet, rows from
memory, injection through a snippet's output, a forged result, code pasted
into the data field). api.md: 422 code, the data-code paragraph under Data
changes, done.dataset.code and the BFF code list. agents/README.md: the
tool and its helpers, the three settings, the scope and prep eval cases.
Changelog fragment run-data-code.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
The run queue becomes pipeline-only after this branch (owner decision of
2026-10-14), so RequestLedger.rate_start (RunQueue.rate) is documented as
the helper every sandbox run of a turn books its start through: run_data_code
today, the plot pipeline once whole turns are no longer rated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Fable security review of 2026-10-14, must-fix items on the branch:

- Harness data mode: the audit hook now refuses every open (reading too),
  every import of a module not loaded yet, os.listdir, os.scandir and other
  listings, and every route to a frame (gi_frame, cr_frame, ag_frame,
  tb_frame through object.__getattr__, sys._getframe). Everything the DATA
  profile allows, every module of pandas.core, pandas.tseries and pandas.api
  and every time zone are loaded before arming; the snippet is read and
  compiled before arming and run with exec; inspect.currentframe answers None
  so pandas' warnings still work; the harness prints tracebacks itself with
  the guard paused. The proven bypasses (open and __import__ through a
  generator frame's f_builtins, a traceback's frame, a fresh import, a
  directory listing) are harness tests that must fail the run. os.stat,
  os.environ reads, function.__globals__ and imports of loaded modules raise
  no audit event in CPython 3.13 and stay the validator's (documented).
- DATA profile literal-data: a list or tuple repeated by an int literal
  counts its copies, and text values count like numbers (elements and dict
  values, never dict keys), 20 per container and 40 per file. Tool docstring,
  root.md and the design doc say so.
- Local Docker backend parity: data runs pass ANYPLOT_RLIMIT_AS_MB and
  ANYPLOT_RLIMIT_NPROC (the renderer's 1024 and 64) and one BLAS thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Brings in #12169 (frame, generator, coroutine, traceback and code-object
attributes in ESCAPE_ATTRIBUTES, both profiles) and #12170 (the round's
changes and the user's standing requests). No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
The escape names #12169 added (gi_frame, f_builtins, tb_frame, ...) reach
the DATA profile through ESCAPE_ATTRIBUTES: the Fable review's bypasses
through a generator's or a traceback's frame never validate.

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

Owner question 2026-10-14 (Lorenz attractor): a request in words that
needs numerical integration, iteration, random numbers or several steps
goes to run_data_code, a closed formula to generate_data. Three prep
cases described in words (Lorenz ODE, logistic map, random walk); on the
real model all three chose run_data_code and the four formula cases
generate_data (the fake renderer's fixture table made the root re-run,
so size is checked only with a real renderer). The prep table no longer
crashes on a code call compared without its size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Brings in #12171 (the dataset judge returns the headers' language, the
notes screen returns the notes' language, the session language from a
paste). Conflicts:

- agents/main.py: main changed _judge_dataset to return the headers'
  language; this branch moved it into dataset_guard.py. The route keeps
  calling check_dataset, which now returns the headers' language from
  judge_dataset (the first part's verdict); run_data_code ignores it.
- docs/reference/api.md: main's text taken, this branch's four additions
  (422 code, the data-code paragraph, done.dataset.code, the BFF code)
  applied on top.
- docs/concepts/agent-network.md and agents/README.md: both sides kept
  (the language field and source next to 422 code and run_data_code; the
  scope case count is 219, the prep case count 44 with 11 data-code cases).

scope_judge.md, scope.evalset.json (ids unique), policy, scope_guard and
import_case merged without conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Copilot AI balanced review requested due to automatic review settings October 14, 2026 13:10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Comment thread agents/anyplot/data/classify.py Fixed
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

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.

🔵 Needs a closer look

The security-sensitive sandbox and deployment changes need human review, and request-budget accounting plus contributor documentation remain unresolved.

2 open findings
What changed in this PR

Adds sandboxed data generation from user code, spanning validation, execution, ingestion, API, UI, documentation, and tests.

Changes:

  • Adds run_data_code with validation, quotas, dataset judging, and version storage.
  • Adds renderer data mode with sandbox guards and /run-data.
  • Adds code-paste routing, generated-code UI, documentation, evals, and tests.
File Description
agents/​README.md Documents data-code architecture and settings.
agents/​anyplot/​agent.py Adds data-code session context.
agents/​anyplot/​code/​data_code.py Prepares and validates snippets.
agents/​anyplot/​code/​validate.py Adds the DATA validation profile.
agents/​anyplot/​data/​classify.py Classifies code and formula pastes.
agents/​anyplot/​data/​store.py Stores generated source code.
agents/​anyplot/​dataset_guard.py Centralizes dataset safety checks.
agents/​anyplot/​dataset_versions.py Propagates generated-code metadata.
agents/​anyplot/​opening.py Adjusts dataset judge inputs.
agents/​anyplot/​plugins/​ledger.py Adds data-run accounting.
agents/​anyplot/​plugins/​tool_safety.py Defines tool limits.
agents/​anyplot/​prompts/​root.md Guides data-tool selection.
agents/​anyplot/​prompts/​scope_judge.md Expands the scope rubric.
agents/​anyplot/​render/​backends/​fake.py Adds fake data execution.
agents/​anyplot/​render/​backends/​local.py Runs data snippets locally.
agents/​anyplot/​render/​backends/​remote.py Calls remote /run-data.
agents/​anyplot/​render/​backends/​sandbox.py Rejects unsupported data runs.
agents/​anyplot/​render/​contract.py Defines data-run contracts.
agents/​anyplot/​render/​gates.py Supports snippet error summaries.
agents/​anyplot/​render/​harness.py Adds guarded data execution mode.
agents/​anyplot/​render/​runtimes/​python.py Exposes the harness source.
agents/​anyplot/​render/​serial.py Shares renderer slots with data runs.
agents/​anyplot/​run_queue.py Rates sandbox starts.
agents/​anyplot/​settings.py Adds data-code settings.
agents/​anyplot/​tools/​session.py Implements run_data_code.
agents/​evals/​fixtures/​prep/​de-code-lehrbuch-formel.json Adds a German formula case.
agents/​evals/​fixtures/​prep/​de-code-r-rnorm-seq.json Adds a German R case.
agents/​evals/​fixtures/​prep/​de-code-worte-logistische-abbildung.json Adds a described computation case.
agents/​evals/​fixtures/​prep/​en-code-general-script.json Adds an out-of-scope script case.
agents/​evals/​fixtures/​prep/​en-code-javascript-loop.json Adds a JavaScript case.
agents/​evals/​fixtures/​prep/​en-code-numpy-normal.json Adds a NumPy case.
agents/​evals/​fixtures/​prep/​en-code-python-random-walk.json Adds a Python random-walk case.
agents/​evals/​fixtures/​prep/​en-code-reads-a-file.json Adds a prohibited file-read case.
agents/​evals/​fixtures/​prep/​en-code-strip-plot-calls.json Tests removal of plotting calls.
agents/​evals/​fixtures/​prep/​en-code-words-lorenz.json Adds a described Lorenz case.
agents/​evals/​fixtures/​prep/​en-code-words-random-walk.json Adds a described random-walk case.
agents/​evals/​matrix.py Records generated dataset sizes.
agents/​evals/​prep.py Scores data-code preparation.
agents/​evals/​scope.evalset.json Adds data-code scope cases.
agents/​main.py Routes code pastes and dataset guards.
agents/​renderer/​cloudbuild.yaml Adds deployed data-run smoke coverage.
agents/​renderer/​executor.py Executes and collects data outputs.
agents/​renderer/​main.py Adds the /run-data endpoint.
agents/​renderer/​wire.py Defines renderer wire models.
api/​routers/​agent.py Maps the new code response.
app/​src/​hooks/​useAgentSession.test.ts Tests code-paste session behavior.
app/​src/​hooks/​useAgentSession.ts Sends eligible code to chat.
app/​src/​lib/​agent.ts Extends frontend data types.
app/​src/​sections/​agent-chat/​DataPanel.test.tsx Tests code offers and disclosure.
app/​src/​sections/​agent-chat/​DataPanel.tsx Displays and forwards generated code.
changelog.d/​run-data-code.md Adds the changelog fragment.
docs/​concepts/​agent-network.md Documents architecture and threats.
docs/​reference/​api.md Documents API behavior and payloads.
tests/​unit/​agents/​code/​test_data_code.py Tests snippet preparation.
tests/​unit/​agents/​code/​test_validate.py Tests DATA validation rules.
tests/​unit/​agents/​data/​test_classify.py Tests paste classification.
tests/​unit/​agents/​evals/​test_prep.py Tests data-code eval scoring.
tests/​unit/​agents/​evals/​test_scope.py Tests expanded scope fixtures.
tests/​unit/​agents/​renderer/​helpers.py Adds renderer test helpers.
tests/​unit/​agents/​renderer/​test_executor.py Tests sandbox data execution.
tests/​unit/​agents/​renderer/​test_remote_backend.py Tests remote data requests.
tests/​unit/​agents/​renderer/​test_service.py Tests renderer service behavior.
tests/​unit/​agents/​renderer/​test_wire.py Tests data wire constraints.
tests/​unit/​agents/​runtime/​test_data_code_tool.py Tests the root tool flow.
tests/​unit/​agents/​runtime/​test_harness_data.py Tests harness guards and outputs.
tests/​unit/​agents/​runtime/​test_policy.py Updates scope-rubric assertions.
tests/​unit/​agents/​runtime/​test_registry.py Registers the new root tool.
tests/​unit/​agents/​runtime/​test_render.py Tests backend data execution.
tests/​unit/​agents/​runtime/​test_run_queue.py Tests data-run rate booking.
tests/​unit/​agents/​runtime/​test_serial_render.py Tests shared renderer slots.
tests/​unit/​agents/​runtime/​test_service_flow.py Updates guard imports.
tests/​unit/​agents/​runtime/​test_stream.py Tests enlarged stream payloads.
tests/​unit/​api/​test_agent_router.py Tests the code response mapping.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread agents/anyplot/dataset_guard.py Resolved
Comment thread docs/reference/api.md Resolved
…inst the request

Copilot/CodeQL review of #12172:
- The classifier reads stripped lines capped at 400 characters, and no
  two quantifiers compete for the same spaces before an anchor, so the
  patterns stay linear (CodeQL polynomial regex on ')' / 'if ' + spaces).
- The dataset judge books its weighted tokens into the request ledger
  too, so the root's next budget check inside a run_data_code turn counts
  them.

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

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.

🔵 Needs a closer look

The paste classifier rejects some valid minimal tables, and the root prompt contradicts the required formula-routing policy.

0 open findings

2 resolved since last review
Previously missed (3)

In code that hasn't changed since last review

Medium severity Preserve table classification for minimal CSVs with code-like cells

agents/​anyplot/​data/​classify.py:77

A minimal valid table with one code-looking cell is misclassified. For example, name,value\nprint(x),3\n has one strong/shaped line out of two, so this >= 0.5 check returns code and the dataset route responds 422 code instead of parsing the CSV. This contradicts the intended “one code-like cell stays a table” behavior; make table structure part of the decision (and add the two-line case) rather than relying only on the line ratio.

Medium severity Route closed simple formulas deterministically to generate_data

agents/​anyplot/​prompts/​root.md:79

This says a closed one- or two-variable formula may use either tool, but the PR contract and line 70 require such formulas to stay on generate_data; only integration, iteration, randomness, cumulative sums, or dependent steps should consume a sandbox run. Leaving the choice optional makes the root's routing nondeterministic and can spend rate/daily data-run budget for computations handled by the safe formula evaluator.

Low severity Update fixture count and estimated evaluation duration

agents/​evals/​prep.py:60

The fixture count is now 44 (as asserted in tests/unit/agents/evals/test_prep.py:80), not 41. At the documented 12-second spacing, that is about 9 minutes, so the operational guidance is stale.

🧠 Review effort: Balanced

MarkusNeusinger and others added 2 commits October 14, 2026 15:57
CI's lint job runs mypy on agents/ too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Resolves the dataset-route row of docs/reference/api.md: main's JSON-stat
passage (#12173) and this branch's 422 code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
@MarkusNeusinger
MarkusNeusinger merged commit 132f55b into main Oct 14, 2026
15 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the feat/run-data-code branch October 14, 2026 14:49
MarkusNeusinger added a commit that referenced this pull request Oct 14, 2026
## Summary

- **Only sandbox work queues now.** In the owner's live test of
2026-10-14, plain chat answers and even scope refusals waited about 60 s
each for the run queue's rate window. Owner decision: "queue only the
pipeline … bei sowas muss schnell die antwort kommen".
- A `/messages` turn now starts at once. The ADK run goes into its own
task, and the stream reads an outbox that carries the ADK events and the
queue positions.
- `plot_pipeline` and `run_data_code` take the turn's run slot through
`TurnSlot.take` (`ledger.run_slot`, which replaces #12172's
`rate_start`). The slot books the one start per turn in the rate window
and is held until the turn ends.
- The request deadline pauses while the turn is queued and re-arms in
full when the slot is granted.
- A full queue or an expired wait ends the turn with
`error{code:"capacity"}`.
  - The turn-level `rated = …` gate and `RunQueue.rate` are removed.
  - The theme toggle was already outside the queue and is unchanged.
- **What a user sees:**
- A plain question: `ready` → `message` → `done`, with no wait, even
while another user's pipeline holds the slot.
- A plot: `ready` → `status{queued}` (only when it must wait) → the
pipeline steps → `plot` → `message` → `done`.
- **Per-user message limits replace the rate window as the chat's abuse
guard.**
- `AGENT_USER_MESSAGES_PER_MINUTE` (10) and
`AGENT_USER_MESSAGES_PER_DAY` (200) are checked before the scope judge,
so a flood costs no model call.
  - Action turns count; refused turns do not.
- Fixed refusals `rate_limited` and `daily_limit`, in all 11 refusal
languages.
- **A session closes after 3 scope refusals**
(`AGENT_SESSION_REFUSALS`).
- The count comes from the session's own `out_of_scope` placeholders:
`attack` stores the same placeholder, and `needs_data` has its own and
never counts.
- The third refusal answers `session_closed`, and every later message is
refused before the judge.
  - The per-user `attack` strike lockout is unchanged.
- **Plausible.** The three new refusal codes are recorded under their
own `agent_guardrail_block` reason.

## Notes for the owner

- A closed session can still paste data. The dataset route keeps its own
judge and per-user strikes. Blocking pastes too would be a small
follow-up.
- There is no "new session" button: the `session_closed` text asks the
user to reload.
- A pipeline call that is then refused with `capacity` still counts
against the daily pipeline runs, as before.
- The new refusal texts in 10 languages were written by the implementer
and deserve a native-speaker glance.

## Plan

N/A

## Test plan

- [x] `pytest tests/unit tests/integration`: 10,506 passed. The new
`test_turn_limits.py` covers:
  - a chat turn answering at once while another pipeline runs;
  - the queued status mid-turn;
  - one rated start per pipeline turn;
  - the per-minute and per-day limits;
  - the session closing after 3 refusals, with `needs_data` not counted.
- [x] ruff, ruff format, mypy (`api core agents`) and the changelog
check pass.
- [x] Frontend: lint, Prettier, type-check, 1,023 Vitest tests and build
pass.
- [ ] After deploy: in the live chat, a refusal and a data question
answer within seconds, and a plot run shows queued only when another run
holds the slot.

---------

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.

3 participants