Skip to content

feat(agents): ADK runtime core, provider switch and /v1 service - #12111

Merged
MarkusNeusinger merged 36 commits into
mainfrom
feat/agents-runtime
Oct 9, 2026
Merged

MarkusNeusinger merged 36 commits into
mainfrom
feat/agents-runtime

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

  • Runtime core of the "Use with my data" agent network on ADK 2.11: provider switch (anthropic-vertex with Claude Haiku 5.5 as the default, gemini with Gemini 3.8 Flash), the root agent, one adapter per enabled library, the reviewer, the plot_pipeline workflow, the ScopeGuard, Budget and ToolSafety plugins with the request ledger, the /v1 FastAPI service with the anyplot/1 SSE translator, the render contract, harness, gates and PNG hardening with fake, local Docker and sandbox-stub backends, and a development fixture so adk web agents runs offline.
  • Hardening from an adversarial review round (28 findings, all applied): the pipeline closes its isolation scope so the next turn reaches the root, untrusted text is fenced everywhere a model reads it, the output sanitiser covers tilde and indented code blocks and more link forms, THEME and the theme tokens survive full-code plans, ENVIRONMENT fails closed, abandoned runs expire, one run per user, Claude runs with thinking disabled and forced tool use for structured output, Claude prompts get cache breakpoints, a plan may add at most 2,000 characters of new literals.
  • BFF follow-ups: Binding.role uses the agents contract pattern (12 catalogue specs have roles like temperature_K), and the retryable agent codes guard_unavailable, capacity, no_dataset and rate_limited pass through by name; docs/reference/api.md updated.
  • Not in this PR: the sandbox backend is a stub until the spike S and S2 results are folded in (size-limited run-dir volume, run queue, rlimits, PATH, retry on launcher exec failure); the local renderer has no image yet; the request token budget still counts cache reads at full weight.

Plan

docs/concepts/agent-network.md (design doc) and the session plan; the implementation follows the "Architecture" and "Guardrail layers" sections. ADK behaviours that differ from the plan are documented in agents/README.md and the module docstrings (no response_schema for Claude, effort instead of thinking level, isolation scopes for sub-agents, content-free progress events).

Test plan

  • uv run ruff check . and uv run ruff format --check . clean
  • uv run --extra typecheck --extra agents mypy api core agents: no issues in 92 files
  • ENVIRONMENT=test uv run pytest tests/unit tests/integration: 7,689 passed, 1 skipped (existing MonoLisa skip)
  • uv run python -m tools.changelog check --base origin/main and uv lock --check pass
  • End-to-end "Create plot" through /v1 in-process for both providers with scripted models and the fake renderer; a two-turn session; reviewer rejection plus repair; not_ready, 413/404/409/422/403/503 routes; a real matplotlib render through normalise, edits, loader, harness and gates at exactly 3200x1800; a log canary shows no message or cell content in any log record
  • adk web agents discovers the app with the fixture (/list-apps returns ["anyplot"]); uvicorn agents.main:app answers /v1/status, /docs is 404
  • Live smoke on eu with owner ADC: Haiku 5.5 adapter and reviewer with forced tool use and thinking disabled, two turns, about 0.03 USD; Gemini 3.8 Flash one root call
  • Owner: run adk web agents locally per agents/README.md (with ADK_DISABLE_LOAD_DOTENV=1) and try one "Create plot" on a fixture case

MarkusNeusinger and others added 26 commits October 9, 2026 21:19
anthropic[vertex] gives ADK's anthropic_llm module its AsyncAnthropicVertex
credentials; the lock gains only the google-auth[requests] extra marker.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
AGENT_PROVIDER selects anthropic-vertex (default) or gemini; model ids must
match the provider. Adds the project, judge timeout, render image and
concurrency, idle sweep, accepted audiences and the development-only fixture
seed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
make_model and make_content_config build every agent's model from the
settings; VertexClaude sends a response schema as a forced tool call, which
ADK 2.11 leaves out for Claude. make_judge_client returns the scope and
dataset judge with a 4 s budget and one retry, plus a scripted fake.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
root, adapter, reviewer, scope and dataset judge prompts plus fixed refusals
in English and German. policy.py composes each static instruction from them
and the catalogue sources read verbatim: the style guide, the library rules
and excerpts cut by heading from the repair prompt, the quality criteria and
the review prompt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
RenderJob and RenderResult with the RuntimeAdapter and RenderBackend
protocols; the probe harness; R1-R3 host gates with PNG hardening and the
padding fallback, G3/G5/G7/G8 as advisory lines; the fake and local Docker
backends, and the sandbox backend as a stub until spike S.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…est ledger

The ledger is a context variable the route binds per request; plugins and the
pipeline share it. ScopeGuard judges free text and fails closed, Budget halts
only the root, ToolSafety allowlists tools per agent, validates arguments,
caps results and maps errors to fixed codes. Attribution lines carry no
content.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
The root talks to the user with a constant static instruction plus server-set
session facts; the adapters and the tool-less reviewer run inside the bounded
pipeline (two attempts, one review) under their own isolation scope. App
carries the plugins so adk web finds it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
AGENT_DEV_FIXTURE seeds new sessions from agents/evals/fixtures/cases in
development only; scatter-basic/matplotlib and bar-grouped/seaborn ship with
small synthetic CSVs and build their snapshots from plots/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
FastAPI without docs, the routes the BFF calls, in-memory session and
artifact services, the caller check outside development, and a translator
that emits only sanitised anyplot/1 events.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Fake renderer, scripted judge, ScriptedLlm for Gemini and VertexClaude over a
fake Anthropic client; covers ok, repair, failed, padded canvas, not_ready,
refusal, guard_unavailable and the route errors.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…ary and fixtures

Unit tests with fakes: Claude structured output through the forced tool, the
judge budget, verbatim prompt composition, PNG hardening and gates, a real
harness render of the adapted scatter-basic file, plugin guardrails, the
agent registry, the anyplot/1 translator, the caller check, a log canary and
ADK's loader finding the app. The harness counts only scatter marks for G8,
and the service's content-capture switch is a function the tests call.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
agents/README.md covers what is built, every AGENT_* variable, adk web with
the fixture seed (and ADK_DISABLE_LOAD_DOTENV, since adk web walks up to the
root .env) and the service; the design doc's status line and the repository
tree name the runtime core.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…eline unit tests

The pipeline still yields one PlotResult when the render store refuses the
shipped PNGs. Unit tests cover the result rules, the soft deadline and the
fenced adapter request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…aches the root

ADK treated the never-closed pipeline-<hex> scope as a paused task: the next user
message was stamped with it, hidden from the root, and the next plot_pipeline call
replayed the first run's result. The pipeline now ends its scope with a finish_task
response. A two-turn /v1 regression test covers both providers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…p a late review

An adapter plan or verdict that failed its output schema raised inside ADK, ended
the run as failed/error with no repair, and put the model's answer into an ERROR log
record. An after_model_callback now blanks such an answer, so the adapter gets repair
feedback and an unread review ships as needs_attention with its reason. The review
is also skipped when the soft deadline cannot fit it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…osed on ENVIRONMENT

Haiku 5.5 thinks by default and ADK 2.11 sends the signature-only block back as
redacted_thinking, which would break the root's reply after a tool call; every Claude
config now disables thinking. AGENT_MODEL for anthropic-vertex is limited to models
that accept a forced tool_choice. ENVIRONMENT now defaults to production, and
development is refused when K_SERVICE shows a Cloud Run revision, so a missing
variable can no longer skip the caller check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…e datasets

An active-run entry whose stream never started was never cleared, wedging the session
at 409 and hiding it from the sweeper. Runs now carry a start time and expire after the
request deadline plus a margin, and a background task clears them too. A user gets one
active run across sessions, and the dataset judge refuses every verdict but in_scope.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…iling the run

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…se plot notes

Column names, the spec brief, set_bindings errors and the model-written changes and
residual defects now reach the adapter, the reviewer and the root only inside fences
after the data preamble. The dataset judge sees every header. The stream sanitiser
also omits tilde-fenced and indented code, protocol-relative and mailto links and
unclosed tags, and the plot event's notes go through it. get_dataset_profile trims
itself to its cap, plot_pipeline gets a cap that fits a full PlotResult, and a
pipeline call that ToolSafety refuses no longer counts as a daily run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…t after any plan

The overlap check only covered edits that touched a protected line: a full_code plan
on attempt 2, or an edit adding PAGE_BG = "#FF00FF" further down, shipped as ok.
apply_plan now compares the protected statements of the result with the base and
refuses any further binding of THEME or a theme token.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…iterals

The design's per-plan literal budget was missing; only the 200-character cap per
literal existed. The pipeline now blocks a plan whose new string literals exceed it
and hands the repair a line with the overshoot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
… cache breakpoints

After a library switch, base=previous and get_current_code picked the other
library's last version; versions now carry their library and both filter by it.
Claude calls now carry ADK's prompt-cache breakpoints (App context-cache config,
Claude only, 5-minute lifetime), so the adapter's static instruction is billed as a
cache read after its first use.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…cancelled

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…le agent codes

The BFF's binding role pattern ^[a-z_]{1,32}$ rejected roles of 12 catalogue specs
(temperature_K, X1, lead_aVR, ...); it now copies the agents contract's pattern, and a
test keeps the copies equal. guard_unavailable, capacity, no_dataset and
rate_limited now reach the browser instead of upstream or invalid.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…he fragment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
ADK labels the dynamic instruction as an instruction, but Haiku 5.5 still
replied about the session block in the live smoke. The root prompt now
forbids mentioning, quoting, confirming or comparing it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Copilot AI balanced review requested due to automatic review settings October 9, 2026 19:22
Co-Authored-By: Claude Fable 5.1 <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.

🟡 Changes recommended

Unresolved prompt-injection paths, incomplete render artifacts, and unsafe stderr handling affect security and correctness.

7 open findings
What changed in this PR

Adds the local runtime core for the ADK-based “Use with my data” agent network.

Changes:

  • Adds provider-switched agents, guardrail plugins, and the adaptation pipeline.
  • Adds the private /v1 service, SSE translation, rendering, and PNG hardening.
  • Updates BFF contracts, dependencies, fixtures, documentation, and tests.
File Description
.dockerignore Includes agent prompt inputs.
agents/​README.md Documents the agent runtime.
agents/​anyplot/​__init__.py Exposes the ADK app.
agents/​anyplot/​agent.py Defines agents and plugins.
agents/​anyplot/​briefs.py Builds bounded model briefs.
agents/​anyplot/​code/​edits.py Hardens generated edits.
agents/​anyplot/​dev_fixture.py Adds development seeding.
agents/​anyplot/​models.py Implements provider-backed models.
agents/​anyplot/​opening.py Handles session initialization.
agents/​anyplot/​pipeline.py Implements the plot pipeline.
agents/​anyplot/​plugins/​__init__.py Describes runtime plugins.
agents/​anyplot/​plugins/​budget.py Enforces usage budgets.
agents/​anyplot/​plugins/​ledger.py Tracks request usage.
agents/​anyplot/​plugins/​scope_guard.py Adds scope classification.
agents/​anyplot/​plugins/​tool_safety.py Validates tool traffic.
agents/​anyplot/​policy.py Composes prompts and fences.
agents/​anyplot/​prompts/​adapter.md Defines adapter behavior.
agents/​anyplot/​prompts/​data_judge.md Defines dataset screening.
agents/​anyplot/​prompts/​refusals.yaml Adds fixed refusals.
agents/​anyplot/​prompts/​reviewer.md Defines render review.
agents/​anyplot/​prompts/​root.md Defines root behavior.
agents/​anyplot/​prompts/​scope_judge.md Defines scope screening.
agents/​anyplot/​render/​__init__.py Selects render backends.
agents/​anyplot/​render/​backends/​__init__.py Documents backends.
agents/​anyplot/​render/​backends/​fake.py Adds fixture rendering.
agents/​anyplot/​render/​backends/​local.py Adds Docker rendering.
agents/​anyplot/​render/​backends/​sandbox.py Adds sandbox stub.
agents/​anyplot/​render/​contract.py Defines render contracts.
agents/​anyplot/​render/​gates.py Evaluates render outputs.
agents/​anyplot/​render/​harness.py Runs sandboxed plots.
agents/​anyplot/​render/​png.py Hardens PNG output.
agents/​anyplot/​render/​runtimes/​__init__.py Documents runtimes.
agents/​anyplot/​render/​runtimes/​python.py Implements Python rendering.
agents/​anyplot/​render/​store.py Stores session renders.
agents/​anyplot/​services.py Provides runtime services.
agents/​anyplot/​session_state.py Defines validated state.
agents/​anyplot/​settings.py Adds runtime configuration.
agents/​anyplot/​sub_agents/​__init__.py Documents sub-agents.
agents/​anyplot/​sub_agents/​adapter.py Implements adapters.
agents/​anyplot/​sub_agents/​reviewer.py Implements the reviewer.
agents/​anyplot/​tools/​__init__.py Documents agent tools.
agents/​anyplot/​tools/​session.py Adds session tools.
agents/​evals/​fixtures/​README.md Documents fixture cases.
agents/​evals/​fixtures/​cases/​bar-grouped-seaborn/​case.json Adds grouped-bar fixture metadata.
agents/​evals/​fixtures/​cases/​bar-grouped-seaborn/​data.csv Adds grouped-bar fixture data.
agents/​evals/​fixtures/​cases/​scatter-basic-matplotlib/​case.json Adds scatter fixture metadata.
agents/​evals/​fixtures/​cases/​scatter-basic-matplotlib/​data.csv Adds scatter fixture data.
agents/​main.py Adds the private service.
agents/​stream.py Translates and sanitizes SSE.
api/​routers/​agent.py Aligns BFF roles and errors.
changelog.d/​agents-runtime.md Records the feature.
docs/​concepts/​agent-network.md Updates implementation status.
docs/​reference/​api.md Documents contract changes.
docs/​reference/​repository.md Documents the new structure.
pyproject.toml Adds agent dependencies.
tests/​unit/​agents/​code/​test_edits.py Tests edit hardening.
tests/​unit/​agents/​runtime/​__init__.py Defines runtime tests.
tests/​unit/​agents/​runtime/​conftest.py Adds shared fixtures.
tests/​unit/​agents/​runtime/​fakes.py Adds scripted model fakes.
tests/​unit/​agents/​runtime/​test_canary.py Tests log privacy.
tests/​unit/​agents/​runtime/​test_dev_fixture.py Tests development seeding.
tests/​unit/​agents/​runtime/​test_models.py Tests provider models.
tests/​unit/​agents/​runtime/​test_pipeline.py Tests pipeline behavior.
tests/​unit/​agents/​runtime/​test_plugins.py Tests guardrail plugins.
tests/​unit/​agents/​runtime/​test_policy.py Tests prompt composition.
tests/​unit/​agents/​runtime/​test_registry.py Tests agent registration.
tests/​unit/​agents/​runtime/​test_render.py Tests rendering and gates.
tests/​unit/​agents/​runtime/​test_service_flow.py Tests end-to-end service flow.
tests/​unit/​agents/​runtime/​test_stream.py Tests SSE translation.
tests/​unit/​agents/​test_adk_pin.py Tests dependency pins.
tests/​unit/​agents/​test_schemas.py Tests shared contracts.
tests/​unit/​agents/​test_settings.py Tests runtime settings.
tests/​unit/​api/​test_agent_router.py Tests BFF compatibility.
uv.lock Locks new dependencies.

🧠 Review effort: Balanced


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

Comment thread agents/anyplot/agent.py Resolved
Comment thread agents/anyplot/pipeline.py Resolved
Comment thread agents/anyplot/render/backends/local.py Resolved
Comment thread agents/anyplot/render/gates.py Resolved
Comment thread agents/anyplot/pipeline.py Resolved
Comment thread agents/anyplot/render/gates.py Resolved
Comment thread docs/concepts/agent-network.md Resolved
CodeQL flagged five polynomial-backtracking patterns in agents/anyplot/data/roles.py
(py/polynomial-redos) on this PR. Possessive quantifiers and run-start lookbehinds
remove the ambiguity; a 200,000-space bullet now parses in under 3 ms and the
roles tests are unchanged.

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

codecov Bot commented Oct 9, 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 5 commits October 9, 2026 21:43
- R1 now compares the hardened PNGs with THEMES instead of the outputs the
  backend returned, so a result with only one theme fails the host gates
  with a blocking line instead of passing.
- A padded canvas replaces only the theme that missed: GateReport gains
  shipped_pngs (the hardened set with the padded copies over it), which the
  pipeline ships, so a one-theme padding keeps the other theme's PNG and
  both image artifacts exist.
- error_summary returns the exception class (a builtin, or a qualified
  class from the plotting stack) and the plot.py line, never the message:
  ValueError(df["secret"].iloc[0]) carried a cell with no quotes to elide.
- The local Docker backend drains stderr while the container runs and
  keeps only its last STDERR_LIMIT bytes, instead of buffering the whole
  stream through communicate() (16 MiB of stderr per theme peaked at about
  50 MB of host memory before, under 1 MB now).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…n text

- The root's session block (an InstructionProvider, sent with instruction
  priority) names the spec id, library, locale, counts and statuses only.
  The spec title started as a public issue, so a title with a newline and
  instruction text no longer lands there; the root reads it fenced as
  <spec_text> through get_spec_brief. root.md says so.
- The adapter request carries the readiness hints (they quote catalogue
  lines), the feedback (failed edits and validator findings quote
  model-written code, reviewer lines are model-written) and the previous
  plan as one JSON object inside a <tool_notes> fence, the way the
  plot_pipeline result reaches the root. The reviewer's gate notes go into
  a <tool_notes> fence too, so both requests hold nothing but fixed headings
  and validated identifiers outside a fence. adapter.md and reviewer.md
  describe the blocks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…ude default

- The status line lists what is built (including bindings.apply, which the
  PUT bindings route and the set_bindings tool call) and what is not, dated
  2026-10-09.
- The Model decision records the owner's 2026-10-09 choice: Claude Haiku 5.5
  on Vertex AI (eu) is the phase-1 default for every agent and the judge;
  Gemini 3.8 Flash, with the judge on gemini-3.5-flash-lite, is the second
  arm behind AGENT_PROVIDER. The agents table, the model factory and
  settings rules, the production environment, the baselines, the harness
  command, the local run block (now with ENVIRONMENT=development, which
  AGENT_RENDERER=local requires), GET /v1/status and the risk rows follow.
- The fencing section covers <spec_text>, <tool_notes> and the class-only
  render error line; R3 pads only the theme that missed.
- docs/index.md and the project guide drop "Gemini" and the skeleton-only
  status; the changelog fragment names the render-error and both-themes
  rules.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
…ge family without a regex

The render error summary now accepts only builtin exceptions and an exact list of
plotting-library exception classes, so a made-up dotted name such as
pandas.Alice_Smith_4111Error can no longer carry data into the repair feedback.
A canvas miss of one theme names that theme instead of (both). The role-range
family (y1, yn) comes from string operations instead of the last regex CodeQL
flagged as polynomial.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
Spike M, spike X, spike A' and the exit criteria name the Claude arm next to
Gemini, and the harness and CI inputs carry a provider setting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
CodeQL moved its polynomial-regex alert to the last remaining role regex; the
helper now matches the old pattern on 20,000 random names without a regex.

Co-Authored-By: Claude Fable 5.1 <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.

Comment thread agents/anyplot/render/__init__.py Resolved
Comment thread agents/anyplot/sub_agents/adapter.py Resolved
Comment thread docs/index.md Resolved
MarkusNeusinger and others added 2 commits October 9, 2026 22:14
…ne library registry

AGENT_RENDERER=fake returns fixture PNGs and runs no code, so the settings and
make_backend refuse it unless ENVIRONMENT is development or test. AGENT_LIBRARIES
is validated against the runtime registry (the normaliser's libraries), so
/v1/status, eligibility and the adapter registry can no longer disagree; the
adapter construction drops its silent filter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdMSqLR5ww4ji74EUSmk9M
@MarkusNeusinger
MarkusNeusinger merged commit fb5a294 into main Oct 9, 2026
16 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the feat/agents-runtime branch October 9, 2026 20:23
MarkusNeusinger added a commit that referenced this pull request Oct 10, 2026
…12115)

## Summary
- Adds `anyplot-renderer` (`agents/renderer/`): a small FastAPI service
with `POST /render`, `POST /render/{job_id}/cancel` and `GET /status`
behind Cloud Run IAM and the ID-token claims check, that runs each theme
of a job in its own Cloud Run sandbox (`sandbox do`, never `--write` or
egress, explicit `PATH`), one at a time, with the limits spikes S and S2
measured: run directories on a 512 MiB in-memory volume the service
requires on Cloud Run (`503 volume_missing` without it), a watchdog that
kills a run past 64 MiB, 10,000 files or a 512 MiB `MemAvailable` floor,
a kill that counts only once the launcher has exited, one retry when the
launcher fails before the harness starts, bounded output, a cleaned
probe, rlimits, a stop on client disconnect or cancel, JSON `500
internal` without the message, and a self-restart when a launcher
outlives its kill. It only runs code; the host gates stay in the agents
service. The image installs the plotting libraries without ADK;
`agents/renderer/cloudbuild.yaml` builds, deploys a `candidate`, smokes
and promotes it like the API, and creates the service on the first build
only when `describe` reports it missing.
- Adds the `remote` render backend, now the default
(`AGENT_RENDERER=remote`, `AGENT_RENDER_URL`), so a local `adk web` and
the deployed agents service render in the same sandboxes: an ID token
from the metadata server when deployed, or from `AGENT_RENDER_TOKEN` or
the developer's Application Default Credentials in development; retries
over a cold start (1, 3, 8, 15 s within 30 s) for connection errors and
front-end 429/5xx only, never for the renderer's own answers; a cancel
call for abandoned renders; limit and measured value in the repair
feedback.
- Hardens the caller check in both services:
`X-Serverless-Authorization` wins whenever it is present, because Cloud
Run then checks only that header and passes `Authorization` through
unverified. The harness prints a start and an end line and sets soft and
hard limits alike. CI gains a `renderer-image` job (`ci-image.yml`) that
builds the image and renders a seaborn plot through the harness with no
network.

## Plan
Design: `docs/concepts/agent-network.md` ("Render", "Serving and
infrastructure"; status line and service table updated). The renderer
split for phase 1 was decided on 2026-10-10 (one renderer for the
deployed agents and for `adk web`). Follows #12111 and #12112.

## Test plan
- [x] `uv run ruff check . && ruff format --check .`, `mypy api core
agents` (101 files), `pytest tests/unit tests/integration` (7935 passed;
executor tests run the real harness against a fake launcher: kill path,
cancel, disconnect, stuck launcher, retry once and twice, byte, file and
memory watchdog, bounded output, refused output files; service tests
cover the caller check, the slot, low memory, the volume check and
`/status`; the `remote` backend is tested against the in-process app and
once end to end through the real executor and host gates), changelog
check, `uv lock --check`
- [x] Deployed to Cloud Run as a throwaway service
(`anyplot-renderer-spike`, a role-less service account, Cloud Build
`5b86cc7a` from this head with `_SMOKE_RENDER=false`, 3 min 51 s): the
first-build path created the service; `gcloud beta run deploy
--sandbox-launcher` and the repeated `--add-volume` flags are accepted
by the Cloud Build gcloud image (587.0.0); an anonymous call gets 403;
`/status` with the owner's gcloud token reports `sandbox: true`,
`volume: true` (the in-memory volume reports its 512 MiB limit), 3,524
MiB available; a real matplotlib render answered in 2.9 s with a 108 KB
PNG; spike X (122 cases) renders through it from a laptop with
`--gcloud-token`
- [x] Configured build on the throwaway with `_SMOKE_RENDER=true` (Cloud
Build `a89a46b2` from `09f50b62d`, 3 min 47 s): the `--no-traffic`
candidate path, the smoke as the service's own account (impersonated; a
build cannot mint an ID token for itself, which builds `da6f53f5` and
`a9d3ac7f` showed and the two fix commits address), `/status` with the
volume in place, a real render in 3.2 s, promote. The `renderer-image`
CI job ran and passed on every push.
- [x] `/tmp` fill through the promoted revision (the review's open
question): code writing 40 MiB files into the sandbox's private `/tmp`
ends after 4.6 s with `OSError: [Errno 28] No space left on device`; the
instance stays up (3,451 MiB available before, 3,328 after), the memory
watchdog never fires. Decided in the design doc: 4 GiB with one sandbox
at a time, no `RENDERER_BIND_TMP`, no 8 GiB instance. The throwaway
service, its service account and its images are deleted.
- [ ] Not verifiable before merge: the Application Default Credentials
token path against Cloud Run IAM; `.github/workflows/` has no pre-merge
loop
- [ ] After merge nothing deploys: the real `anyplot-renderer` service
account, the first build and the `deploy-renderer` trigger are owner
tasks (design doc, task 14)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
MarkusNeusinger added a commit that referenced this pull request Oct 10, 2026
## Summary
- Adds the admin-only "Use with my data" chat page at
`/debug/agent?spec=&library=&language=`
(`app/src/pages/AgentChatPage.tsx`): data panel with a 200 KB counter,
20-row preview, a column dropdown for every spec role (series families
get one dropdown per member), the chat thread, a progress timeline that
follows the `anyplot/1` stream, a queue-position counter in the chat's
corner, `.stop()` through the cancel route, and library pills. Built
only with `VITE_ENABLE_AGENT_CHAT=true` (off in `app/cloudbuild.yaml`,
so production ships without the chunk) and opens only in local
development or for a browser carrying the admin hint.
- Adds the result card (`app/src/sections/agent-chat/ResultCard.tsx`)
with the plot page's overlay actions (copy image, download PNG, open
full size), a light/dark switch through the theme toggle route, the
adapted `plot.py` with copy and download, `data.csv` download, the
change list and residual notes, a refine composer and the quick-feedback
slot; plus the `.adapt()` overlay button on the plot page, gated by
build flag, admin hint and the BFF's eligibility route so public
visitors never call the debug API.
- Two small contract changes on the service side so the page can address
versions and roles reliably: the `plot` event carries `version` (agents
translator and BFF allowlist), and the dataset answer lists the spec's
`roles` with a 422 that keeps the binding check's lines. Analytics
events (enum properties only) are documented in
`docs/reference/plausible.md`; a loopback mock of the BFF
(`app/scripts/agent-bff-mock.mjs`) drives the whole flow without the
API, the agents service or a model.

## Plan
Design: `docs/concepts/agent-network.md` (sections "Frontend", "Result
card", "Analytics"); this is the frontend PR of the phase-1 roadmap,
built on the merged runtime core (#12111) and run queue (#12112).

## Test plan
- [x] `cd app && yarn lint && yarn fm:check && yarn type-check && yarn
test` (81 files, 750 tests) and `yarn build` with the flag on and off;
the default build contains no agent client
- [x] `uv run --extra dev ruff check . && ruff format --check .`, `mypy
api core agents`, `pytest tests/unit tests/integration` (7816 passed),
changelog check, `uv lock --check`
- [x] Driven in the browser against the mock at 1280 px and 390 px,
light and dark: parse, bindings incl. series families and a refused set,
queued counter, progress timeline, result card actions (clipboard PNG,
downloads named `plot.py`/`data.csv`, open full size, copy code), theme
switch, repair round, refusal, error, stop then a new turn without a
409, ineligible pair; no horizontal scroll, 16 px gutter at 390 px
- [ ] After merge: `deploy-app` and `deploy-api` Cloud Builds succeed;
production stays unchanged (`_VITE_ENABLE_AGENT_CHAT` false,
`AGENT_ENABLED` false)
- [ ] Once the agents service is deployed: open
`/debug/agent?spec=scatter-basic&library=matplotlib` as admin and run
one "Create plot" end to end

---------

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