Skip to content

Interactive rollout#1928

Open
cdreetz wants to merge 15 commits into
mainfrom
interactive-rollout
Open

Interactive rollout#1928
cdreetz wants to merge 15 commits into
mainfrom
interactive-rollout

Conversation

@cdreetz

@cdreetz cdreetz commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Description

New interactive rollout terminal app via uv run vf-play <environment>

  • image rendering
  • dropdown for assistant message or tool call selections
  • parallel tool calls
  • tool display sidebar
  • loading status for environments that take time to setup
  • 'a' to show answer if rollout has one

wordl env

image

tic-tac-toe env

image

loading state view

image

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Test improvement

Testing

  • All existing tests pass when running uv run pytest locally.
  • New tests have been added to cover the changes

Checklist

  • My code follows the style guidelines of this project as outlined in AGENTS.md
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

Additional Notes


Note

Medium Risk
New optional CLI/TUI path with image-loading opt-in and rollout error plumbing changes; large test surface but limited impact on default eval/training flows.

Overview
Adds vf-play so you can step through a single environment rollout as the model instead of calling an API. The command loads one dataset row, runs the normal rollout loop with a HumanClient, and drives a persistent Textual UI for each generation step.

The TUI shows model-visible messages and tool schemas, and lets you submit one assistant turn with optional reasoning, message content, and parallel tool calls built from JSON-schema argument fields (including multi-line strings). Quitting raises InteractiveSessionExit, which is recorded on rollout state like other framework errors. Optional --save, --no-score, and workspace env_id inference match eval-style workflows.

PrimeCLIPlugin gains play_module with the same --env-dir-path injection as eval/gepa. Dependencies add textual and conditional textual-image for inline terminal images; external image URLs stay off unless --allow-external-images.

InterceptionServer now wraps non-Error exceptions and uses State._set_error when storing rollout failures so interactive quit and other typed errors propagate consistently.

Reviewed by Cursor Bugbot for commit 297c11c. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add vf-play interactive terminal UI for human-in-the-loop rollout playthrough

  • Adds a new vf-play CLI command backed by a Textual TUI (app.py) that lets a human step through a model rollout turn-by-turn, composing assistant messages, reasoning, and tool calls with schema-aware inputs.
  • HumanClient (client.py) implements the standard Client interface, routing model requests to the TUI and returning structured Response objects with the human's input.
  • play.py drives the full workflow: resolves env and dataset row, runs one rollout via env._run_rollout_state, renders a summary table, and optionally saves the result to JSON.
  • Image URLs in prompts are rendered in-terminal when the terminal supports it; external URLs require --allow-external-images.
  • Fixes error propagation in InterceptionServer so exceptions during intercepted requests are stored on State as proper Error subclasses rather than being stringified.

Macroscope summarized 297c11c.

@cdreetz cdreetz requested review from hallerite and mikasenghaas July 5, 2026 22:55
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py Outdated
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/scripts/play.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/scripts/play.py
Comment thread verifiers/cli/interactive/app.py
@macroscopeapp

macroscopeapp Bot commented Jul 5, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces a substantial new feature (interactive rollout TUI with ~2000+ lines of new code) that adds new user-facing behavior and components. Additionally, there are 3 unresolved medium-severity review comments identifying potential bugs that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment thread verifiers/scripts/play.py Outdated
Comment thread verifiers/scripts/play.py
Comment thread verifiers/scripts/play.py Outdated
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/scripts/play.py Outdated
- schema_type: resolve anyOf/oneOf so Optional[...] tool args parse to
  their real JSON type instead of a raw string
- gate file://, and bare-path images behind an opt-in flag (renamed
  --allow-remote-images -> --allow-external-images); only inline data:
  images render by default
- infer_current_env_id: keep walking ancestors past nameless pyprojects
- run_interactive_rollout: reject non-object --env-args with a clean
  CLI error instead of a TypeError traceback

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread docs/v0/evaluation.md
Comment thread verifiers/cli/interactive/client.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py
Comment thread verifiers/cli/interactive/app.py Outdated
…cation

- HumanClient.start: race ready.wait() against the app task so a TUI
  startup failure raises instead of hanging forever
- schema_type: only collapse anyOf/oneOf to a single non-null branch
  (Optional[X]); leave genuine unions (int | str) as free-form
- truncate the URL in the image-load error text

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread verifiers/cli/interactive/app.py
Treat only a literally-empty argument field as "not provided", so an
intentionally blank or whitespace-only required string (e.g. empty file
content) is sent verbatim instead of being rejected as missing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread verifiers/cli/interactive/client.py
Comment thread verifiers/cli/interactive/app.py
Resolve conflicts against the nano-as-v1 refactor:
- pyproject.toml: keep textual/textual-image deps + vf-play entry point;
  drop dead tomli and duplicate typing_extensions (min Python is now 3.11)
- interception_utils.py: union imports (Error/State for the quit-error
  path + OPENAI_RESPONSES_OUTPUT_FIELD from main)
- uv.lock: regenerated
- play.py: drop the removed --taskset.*/--harness.* override mechanism;
  env_id is now a plain positional, env args come from --env-args only
- app.py: Select.BLANK -> Select.NULL for textual 8.x

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 297c11c. Configure here.

suffix=suffix,
)
size = Image.open(io.BytesIO(response.content)).size
return ImagePayload(path=path, size=size, source=url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

External images block asyncio loop

Medium Severity

With --allow-external-images, loading http(s) image URLs uses synchronous requests.get inside message rendering on the Textual/asyncio event loop, which can freeze the TUI until the download finishes or times out.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 297c11c. Configure here.

parts.append(
Text(
self._truncate_text(
json.dumps(part_data, indent=2, ensure_ascii=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium interactive/app.py:750

_content_renderables calls json.dumps(part_data, ...) on unknown content-part mappings, which raises TypeError when a part contains non-JSON-native values like bytes, Path, dataclasses, or Pydantic models. This crashes rendering of the entire messages pane for the turn instead of showing a fallback placeholder. Consider using the repo's existing _json_gate_default() as the default= argument (or otherwise pre-converting non-serializable values) so unknown parts degrade gracefully.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/cli/interactive/app.py around line 750:

`_content_renderables` calls `json.dumps(part_data, ...)` on unknown content-part mappings, which raises `TypeError` when a part contains non-JSON-native values like `bytes`, `Path`, dataclasses, or Pydantic models. This crashes rendering of the entire messages pane for the turn instead of showing a fallback placeholder. Consider using the repo's existing `_json_gate_default()` as the `default=` argument (or otherwise pre-converting non-serializable values) so unknown parts degrade gracefully.

items.append(Group(description, table))
return Group(*items)

def _argument_rows(self, tool: Tool) -> list[tuple[str, Mapping[str, Any], bool]]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium interactive/app.py:698

_argument_rows only reads tool.parameters["properties"], so tools with valid non-object schemas (e.g. array/string root) or free-form additionalProperties objects produce zero rows. The form shows No declared arguments. and _build_current_tool_call can only submit {}, making it impossible to pass any arguments to those tools — even though MCP/OpenEnv pass such inputSchema through correctly. Consider supporting schemas whose root type is not object with named properties, or at minimum documenting that the interactive UI only supports object-shaped tool schemas.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/cli/interactive/app.py around line 698:

`_argument_rows` only reads `tool.parameters["properties"]`, so tools with valid non-`object` schemas (e.g. `array`/`string` root) or free-form `additionalProperties` objects produce zero rows. The form shows `No declared arguments.` and `_build_current_tool_call` can only submit `{}`, making it impossible to pass any arguments to those tools — even though MCP/OpenEnv pass such `inputSchema` through correctly. Consider supporting schemas whose root type is not `object` with named properties, or at minimum documenting that the interactive UI only supports `object`-shaped tool schemas.

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.

1 participant