Skip to content

Make a prompt's arguments one checked JSON object - #13

Merged
vinniefalco merged 4 commits into
cppalliance:masterfrom
vinniefalco:master
Nov 14, 2026
Merged

vinniefalco merged 4 commits into
cppalliance:masterfrom
vinniefalco:master

Conversation

@vinniefalco

@vinniefalco vinniefalco commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

Summary

A caller passes a prompt one JSON object. The frontmatter key args: declares the object's shape in a subset of JSON Schema, the run's first step checks the object against it, and the read-only Lua global args holds the checked object. A prompt's arguments then have the form of a tool's arguments, so a Host can publish any prompt as a tool.

The input file, the raw argument string, and the argv global are gone. Nothing recognizes a removed form: an old prompt fails by the ordinary rules.

---
name: search
description: Searches for papers on one query
runvil: 0
args:
  type: object
  properties:
    query: { type: string, description: What to search for }
    limit: { type: integer, default: 3 }
  required: [query]
---

## Search

```lua
return args.query .. ', limit ' .. args.limit
```
  • {"query":"papers"} returns papers, limit 3.
  • {"query":5} ends the run at its first step: argument `query` must be a string, got integer.

What changed

Four commits, in order.

  • 6ef23a8e Remove the input file. The frontmatter has no input: key, RunSetup has no input_text, and the Harness stages nothing. output: is unchanged.
  • 8b7ad5f9 A view behaves like its table. A var table and every read-only table are empty stand-ins over hidden data. #, pairs, and next now read the data, and a view handed to tools.call, tasks.fanout, a spawn's item, or var arrives as its data. var.items[#var.items + 1] = x appends.
  • 3d87c936 The args rule.
    • args: takes the keywords type, properties, required, additionalProperties, items, enum, description, and default. Any other keyword fails the parse, with the YAML path and position of its node.
    • An object is closed unless it says additionalProperties: true. A missing field takes its default, at every depth. A null for a field that is not required counts as the field left out.
    • A prompt with no args: key takes one optional string named text.
    • The check is the second of four start checks. A mismatch ends the run with the new code args_invalid (705), with no location and no event.
    • call(heading, args) and tasks.spawn(target, { args = ... }) take a table of named fields, or a string that the chain reads as args.text.
    • Two smaller fixes are in this commit: a task that holds the answer it waited for reports no block in tasks.status, and the var guard refuses a key with no place in JSON data.
  • 0d938885 Refuse what would be lost. A var write converts by the rules of json.encode, so var.t = { 'a', name = 'x' } raises where it stored ["a"] and dropped name. tasks.spawn, tasks.join_any, and tasks.join refuse an unknown key of opts. The API allowlist loses serde_yaml_ng.

What a Host changes

  • RunSetup.args is the text of one JSON object, and its default is {}. RunSetup.input_text is gone.
  • runvil::prompt::ArgsSchema stands where ArgsDecl, ArgDecl, ArgType, and ArgsDeclIter stood. ArgsSchema::as_json() gives the declaration as plain JSON Schema, ready to publish as a tool's input schema.
  • Frontmatter::input() is gone.
  • ErrorCode::ArgsInvalid is a new code.
  • Event::TaskStarted has args, a JSON value, where it had input, a string.

Both public-api.txt listings are blessed.

Verification

Run locally on Windows on the final tree, all passing:

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets --all-features with CARGO_BUILD_WARNINGS=deny
  • cargo nextest run --locked --workspace --all-features: 3,787 passed
  • cargo doc on the workspace, on runvil-engine, on runvil, and on runvil-runtime with private items, with RUSTDOCFLAGS="-D warnings"
  • cargo +nightly-2026-09-05 xtask api --check
  • cargo dylint --all -- --locked --workspace --all-targets --all-features
  • cargo test -p build-xtask
  • node --test tools/docs-claims.mjs
  • cargo deny check
  • cargo run -p runvil --example run-prompt

The plan and what the build found are in plan/2026-11-13-3-json-args.plan.md.

Related

A prompt declared a file under `input:`, and the Harness wrote the launch's `input_text` at that path before the run, or ended the run when it could not. The key, the `RunSetup` field, and `stage_input` are deleted, so a run starts on the store as the launcher left it. `prepare` has no failure left, so it returns `(Run, RunPacks)`.

- `FileDecl` stays for `output:`. `Frontmatter::input` is deleted.
- `prepare` takes no `run_id`, and `harness.rs` loses the two arms that ended a run on a `prepare` error. `of_run` is part of `refused`, its one caller.
- An `input:` key now fails the parse as an unknown key.
- The tests in `prepare-files.rs` and the round-trip test in `harness.rs` seed `paper.md` through `acquire_store`. `launch.rs` and the `run-prompt` example take their text from `args`.
- Both `public-api.txt` listings lose `Frontmatter::input`, and the `runvil` listing loses `RunSetup::input_text`.
- The three tests of a refused launch are removed with `input_undeclared` and `input_missing`. No test covers a frontmatter that has an `input:` key.

Plan: /plan/2026-11-13-3-json-args.plan.md
A `var` table and an `argv` table are empty proxies over hidden data, so `#` gave 0, `pairs` gave nothing, and every conversion but `json.encode` read them as empty tables. A view now has a `__len`, `pairs` and `next` walk its data table, and `plain` gives `from_value` a copy of the data in place of each view. A value with no view inside converts as it did.

- `read_only_proxy` sets `__len` only when its `index` is a table, and `var.rs` sets it for every `var` table. `view_data` reads a view with `unseal`, so `pairs`, `next`, and `tasks.fanout` use the definition of a view that `json.encode` uses.
- `plain` returns the value itself when it holds no view. A table that holds a view becomes a fresh table that shares its other entries, and a copy keeps the list marker, so an empty list still converts to `[]`.
- `plain` counts each value against a `WalkBudget` and fails with `the value is too large to convert`. It does not descend into a table already on its path or past `PLAIN_DEPTH_LIMIT`, so a table that contains itself fails in `from_value` with the same error as before.
- Six sites call `plain`: the `args` of `tools.call`, a spawn's `item`, a `var` write, `render_item_value`, a bare global in prose, and a message record. `collection_members` takes a view's data table one level deep, so each member stays the author's own value.
- `pairs` returns the view as its state and hands back each nested table as its view, so a write still crosses the guard. A `__pairs` metamethod still wins, and a view whose `__index` is a function walks as before.
- `var.items[#var.items + 1]`, `table.insert`, and `table.remove` now work on a `var` list.
- `pairs` over a sealed Pack global now yields its fields: the test in `prelude-tests.rs` expects `greet` where it expected no entry.
- The run tests in `execute/tests/views.rs` pass `var` tables to a tool call, to `tasks.fanout`, to a spawn `item`, and to prose. No run test passes an `argv` table to them.

Plan: /plan/2026-11-13-3-json-args.plan.md
A run took one argument string that nothing checked, and a prompt read it raw as `args` and parsed as `argv`. A run's arguments are now the text of one JSON object: the `args:` key declares its shape in a subset of JSON Schema, the first step of the run checks the object and ends the run with `args_invalid` at the first mismatch, and the read-only `args` global holds the checked object. A prompt with no `args:` key takes one optional string named `text`.

- `ArgsSchema` replaces `ArgsDecl`, `ArgDecl`, `ArgType`, and `ArgsDeclIter`. A serde visitor reads each node, so a refusal has the YAML path and the position of its node, and `as_json` gives the declaration as JSON Schema with `additionalProperties` written on each object.
- `accept` in `args_check.rs` fills the default of each missing field, makes a whole number under an `integer` node an integer, and reports the first mismatch as `ArgsInvalid` with the facts `Reason` and `Name` and no location. The parser checks each declared default with the same `check`.
- `ChainState::root` calls `accept_args` and keeps a refusal in `args_refusal`, which `check_start` returns after the version gate, so `Run::new` still returns a run for any text.
- A chain holds its `args` as an `Arc<serde_json::Value>`. `derive_argv`, the `argv` substitution root, and `nil_argv` are deleted, and a placeholder reads a field by a key path, as in `{{ args.text }}`.
- The guard on `_G` serves `args` and refuses an assignment to it. `argv.rs` is renamed `args.rs`, and `RESERVED_NAMES` has 62 entries.
- `call` and `tasks.spawn` take `args` as a table of named fields, which `to_json` converts, or as a string, which `with_args` makes the one field `text`. A list is refused with `got a list`. Nothing checks the arguments of a chain against the declaration.
- `TaskStarted` has `args` as an `Option<serde_json::Value>` in place of `input`.
- A required field with a default takes the default, and a `null` for a field that is not required counts as the field left out.
- The setup of a run has `{}` as its default `args`.
- `greet` declares a required `name`, `research-person` and `analyst-example` declare a required `text`, and `echo` returns `args.text`.
- Both `public-api.txt` listings change, and neither names `serde_yaml_ng` after the change.
- `Chain::blocked` gives `None` for a chain that holds the answer it was parked on, unless it waits for its slots. `tasks.status` read `blocked` as `chat` for a task whose reply had arrived and whose round was already in `turns`.
- The `var` guard refuses a key with no place in JSON data at the assigning line: `key_refusal` takes a string, or under the root the index of an item the table holds or of the next one. Such a key passed the write and failed the read-back of `var` with a raw `deserialize error`, or as an internal fault for an integer key of the root.
- A table that the conversion of a `var` write refuses raises `must be JSON data:` with the reason of the conversion. A table with list items and named keys still loses the named keys in a `var` write.
- No test names `argv`, the string form of `args`, or a spawn option `input`. A spawn option that `tasks.spawn` does not know is ignored, as before, and no test covers that.

Plan: /plan/2026-11-13-3-json-args.plan.md
A `var` write of a table with list items and named keys stored the list items alone, and `tasks.spawn` ignored an option that it did not take. A `var` write now converts its value by the rules of `json.encode` and refuses a write that would leave its table neither a list nor an object, and `tasks.spawn`, `tasks.join_any`, and `tasks.join` refuse an unknown key of `opts`.

- `to_stored_json` runs the `Encoder` of `json.encode` with the target as the root of each path, so `var.t = { 'a', name = 'x' }` raises `var.t mixes list items and named keys`. It sets `keeps_floats`, so a float with no fractional part reads back as a float.
- `write_refusal` replaces `key_refusal`. It also refuses a named field in a list, a list item in a table that holds named fields, and the removal of an item that is not the last of its list.
- A table that contains itself is refused as `contains itself`, and NaN as `is not a finite number`. The conversion of the Lua binding gave its own text for the first and stored the second.
- `check_value` and `CHECK_DEPTH_LIMIT` are deleted from `walk_budget.rs`. The `Encoder` spends the same `WalkBudget`, so a value too large to walk still raises `is too large to store`.
- `check_keys` in `__impl_tasks.lua` walks `opts` with the Engine's `pairs`, so the first unknown key in sorted order is the one named, and a table read from `var` is checked as its data. The refusal of a spawn names the key and lists `args`, `item`, and `index`.
- `ALLOWLIST` in `build-xtask` loses `serde_yaml_ng`, which neither public API listing names.
- No test covers a `var` table nested more than 127 levels deep, which the `Encoder` refuses.

Plan: /plan/2026-11-13-3-json-args.plan.md
@coderabbitai

coderabbitai Bot commented Nov 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 167 files, which is 17 over the limit of 150.

To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to Team to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 707b12c4-6d3f-4b13-ae6e-cfb24e7ff01f
Commits

Reviewing files that changed from the base of the PR and between 9dd953b and 0d93888.

Files ignored due to path filters: 1

  • Cargo.lock is excluded by !**/*.lock
Files selected for processing: 167

  • crates/build-xtask/src/api/items-tests.rs
  • crates/build-xtask/src/api/items.rs
  • crates/harness-gateway-client/src/wire/request-tests.rs
  • crates/harness-internal/runner/src/effect_loop-tests.rs
  • crates/harness-internal/runner/src/files.rs
  • crates/harness-internal/runner/src/harness.rs
  • crates/harness-internal/runner/src/lib.rs
  • crates/harness-internal/runner/src/packs-tests.rs
  • crates/harness-internal/runner/src/prepare.rs
  • crates/harness-internal/runner/src/recorder.rs
  • crates/harness-internal/runner/src/setup.rs
  • crates/harness-internal/runner/tests/it/effect_loop-broker.rs
  • crates/harness-internal/runner/tests/it/effect_loop-cap.rs
  • crates/harness-internal/runner/tests/it/harness.rs
  • crates/harness-internal/runner/tests/it/prepare-args.rs
  • crates/harness-internal/runner/tests/it/prepare-files.rs
  • crates/harness-internal/runner/tests/it/prepare.rs
  • crates/harness-internal/runner/tests/it/support.rs
  • crates/runvil-engine/examples/greeter.rs
  • crates/runvil-engine/public-api.txt
  • crates/runvil-engine/src/lib.rs
  • crates/runvil-engine/tests/suite/args.rs
  • crates/runvil-engine/tests/suite/event.rs
  • crates/runvil-engine/tests/suite/ids.rs
  • crates/runvil-engine/tests/suite/main.rs
  • crates/runvil-engine/tests/suite/prompt.rs
  • crates/runvil-engine/tests/suite/shipped.rs
  • crates/runvil-engine/tests/suite/support/scenario.rs
  • crates/runvil-internal/lua/benches/surface.rs
  • crates/runvil-internal/lua/src/__impl_coro.lua
  • crates/runvil-internal/lua/src/__impl_globals.lua
  • crates/runvil-internal/lua/src/__impl_tasks.lua
  • crates/runvil-internal/lua/src/args.rs
  • crates/runvil-internal/lua/src/collection.rs
  • crates/runvil-internal/lua/src/error_value.rs
  • crates/runvil-internal/lua/src/globals-tests.rs
  • crates/runvil-internal/lua/src/globals.rs
  • crates/runvil-internal/lua/src/iteration-tests.rs
  • crates/runvil-internal/lua/src/iteration.rs
  • crates/runvil-internal/lua/src/json.rs
  • crates/runvil-internal/lua/src/json/encode.rs
  • crates/runvil-internal/lua/src/lib.rs
  • crates/runvil-internal/lua/src/messages-tests-list.rs
  • crates/runvil-internal/lua/src/messages.rs
  • crates/runvil-internal/lua/src/prelude-tests-environment.rs
  • crates/runvil-internal/lua/src/prelude-tests.rs
  • crates/runvil-internal/lua/src/prelude.rs
  • crates/runvil-internal/lua/src/prose.rs
  • crates/runvil-internal/lua/src/protocol/parse.rs
  • crates/runvil-internal/lua/src/protocol/parse/tasks.rs
  • crates/runvil-internal/lua/src/protocol/parse/tools.rs
  • crates/runvil-internal/lua/src/protocol/request.rs
  • crates/runvil-internal/lua/src/protocol/tests.rs
  • crates/runvil-internal/lua/src/protocol/tests/parse.rs
  • crates/runvil-internal/lua/src/protocol/tests/parse_args.rs
  • crates/runvil-internal/lua/src/protocol/tests/parse_tasks.rs
  • crates/runvil-internal/lua/src/protocol/tests/parse_tool_call.rs
  • crates/runvil-internal/lua/src/proxy.rs
  • crates/runvil-internal/lua/src/test_support.rs
  • crates/runvil-internal/lua/src/test_vm.rs
  • crates/runvil-internal/lua/src/tests.rs
  • crates/runvil-internal/lua/src/tests/args.rs
  • crates/runvil-internal/lua/src/tests/argv.rs
  • crates/runvil-internal/lua/src/tests/budgets.rs
  • crates/runvil-internal/lua/src/tests/json/views.rs
  • crates/runvil-internal/lua/src/tests/logging.rs
  • crates/runvil-internal/lua/src/tests/require.rs
  • crates/runvil-internal/lua/src/tests/require/environment.rs
  • crates/runvil-internal/lua/src/tests/sandbox.rs
  • crates/runvil-internal/lua/src/tests/section_vm.rs
  • crates/runvil-internal/lua/src/tests/store.rs
  • crates/runvil-internal/lua/src/tests/store_reports.rs
  • crates/runvil-internal/lua/src/tests/sys_and_var.rs
  • crates/runvil-internal/lua/src/tests/tool_scoping.rs
  • crates/runvil-internal/lua/src/tests/views.rs
  • crates/runvil-internal/lua/src/var.rs
  • crates/runvil-internal/lua/src/vm/inject.rs
  • crates/runvil-internal/lua/src/vm/install.rs
  • crates/runvil-internal/lua/src/walk_budget.rs
  • crates/runvil-internal/prompt/Cargo.toml
  • crates/runvil-internal/prompt/README.md
  • crates/runvil-internal/prompt/src/contract.rs
  • crates/runvil-internal/prompt/src/contract/args.rs
  • crates/runvil-internal/prompt/src/contract/args_check.rs
  • crates/runvil-internal/prompt/src/contract/tests-args-check.rs
  • crates/runvil-internal/prompt/src/contract/tests-args.rs
  • crates/runvil-internal/prompt/src/contract/tests.rs
  • crates/runvil-internal/prompt/src/detail.rs
  • crates/runvil-internal/prompt/src/frontmatter.rs
  • crates/runvil-internal/prompt/src/lib.rs
  • crates/runvil-internal/prompt/src/parse.rs
  • crates/runvil-internal/prompt/src/tests/frontmatter.rs
  • crates/runvil-internal/runtime/benches/models_loop.rs
  • crates/runvil-internal/runtime/src/execute/context.rs
  • crates/runvil-internal/runtime/src/execute/requirements-tests.rs
  • crates/runvil-internal/runtime/src/execute/run.rs
  • crates/runvil-internal/runtime/src/execute/run/tests-drops.rs
  • crates/runvil-internal/runtime/src/execute/run/tests-inbox.rs
  • crates/runvil-internal/runtime/src/execute/run/tests-reply.rs
  • crates/runvil-internal/runtime/src/execute/run/tests-send.rs
  • crates/runvil-internal/runtime/src/execute/run/tests-start.rs
  • crates/runvil-internal/runtime/src/execute/run/tests-steer.rs
  • crates/runvil-internal/runtime/src/execute/run/tests.rs
  • crates/runvil-internal/runtime/src/execute/scheduler/admission-tests.rs
  • crates/runvil-internal/runtime/src/execute/scheduler/admission.rs
  • crates/runvil-internal/runtime/src/execute/scheduler/chain_record.rs
  • crates/runvil-internal/runtime/src/execute/scheduler/dispatch.rs
  • crates/runvil-internal/runtime/src/execute/scheduler/drive.rs
  • crates/runvil-internal/runtime/src/execute/scheduler/tasks.rs
  • crates/runvil-internal/runtime/src/execute/section_context.rs
  • crates/runvil-internal/runtime/src/execute/section_vm.rs
  • crates/runvil-internal/runtime/src/execute/tests.rs
  • crates/runvil-internal/runtime/src/execute/tests/exec_flow.rs
  • crates/runvil-internal/runtime/src/execute/tests/live_infer.rs
  • crates/runvil-internal/runtime/src/execute/tests/model_and_reply.rs
  • crates/runvil-internal/runtime/src/execute/tests/run_inputs.rs
  • crates/runvil-internal/runtime/src/execute/tests/scheduler/preamble/entry.rs
  • crates/runvil-internal/runtime/src/execute/tests/serial_driver.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/args_surface-chains.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/args_surface-refusals.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/args_surface.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/exec_flow/prompt_reads.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/exec_flow/run_setup.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/exec_flow/store_failures.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/execution.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/global_metatable.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/parsing.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/prepare.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/require/loading.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/require/prose.rs
  • crates/runvil-internal/runtime/src/execute/tests/suite/vfs.rs
  • crates/runvil-internal/runtime/src/execute/tests/tasks.rs
  • crates/runvil-internal/runtime/src/execute/tests/timeouts.rs
  • crates/runvil-internal/runtime/src/execute/tests/tool_scoping.rs
  • crates/runvil-internal/runtime/src/execute/tests/unified_pipeline.rs
  • crates/runvil-internal/runtime/src/execute/tests/views.rs
  • crates/runvil-internal/runtime/src/execute/tests/waits.rs
  • crates/runvil-internal/runtime/src/lua/tests.rs
  • crates/runvil-internal/runtime/src/lua/tests/globals.rs
  • crates/runvil-internal/runtime/src/lua/tests/shims.rs
  • crates/runvil-internal/runtime/src/model/tests.rs
  • crates/runvil-internal/runtime/src/subst-tests.rs
  • crates/runvil-internal/runtime/src/subst.rs
  • crates/runvil-internal/runtime/src/test_support/recording/forward-tests-variants.rs
  • crates/runvil-internal/runtime/src/test_support/recording/forward.rs
  • crates/runvil-internal/runtime/src/test_support/recording/observation.rs
  • crates/runvil-internal/runtime/src/test_support/scenario.rs
  • crates/runvil-internal/runtime/tests/prompts/execution/prologue-return.md
  • crates/runvil-internal/runtime/tests/prompts/execution/store-fallthrough.md
  • crates/runvil-internal/runtime/tests/prompts/valid/prologue-prose-epilog.md
  • crates/runvil-internal/runtime/tests/prompts/valid/require-library.md
  • crates/runvil-internal/types/src/emitter.rs
  • crates/runvil-internal/types/src/error-tests.rs
  • crates/runvil-internal/types/src/error.rs
  • crates/runvil-internal/types/src/event-tests.rs
  • crates/runvil-internal/types/src/event.rs
  • crates/runvil/examples/run-prompt.rs
  • crates/runvil/public-api.txt
  • crates/runvil/src/lib.rs
  • crates/runvil/tests/suite/launch.rs
  • crates/runvil/tests/suite/vfs.rs
  • plan/2026-11-13-3-json-args.plan.md
  • prompts/analyst-example.md
  • prompts/echo.md
  • prompts/greet.md
  • prompts/research-person.md

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@greptile-apps

greptile-apps Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[High impact] The PR changes argument handling across prompt parsing, run startup, Lua sections, child chains, and Host-facing APIs.

The PR appears safe to merge, with a non-blocking fix recommended for cyclic views sent to tools.

Findings

  1. P2 Cycles become empty objects ▶
Fix with agent prompt
### Issue 1
crates/runvil-internal/lua/src/proxy.rs:211-212
`PlainWalk::value` leaves a view unchanged when its hidden table is already on the current path. For example, `local data = {}; local view = setmetatable({}, { __index = data, __metatable = 'view' }); data.self = view` passed to `tools.call` becomes `{"self":{}}`. The copied field still holds the empty stand-in, which `from_value` reads as `{}`.

Refuse the cycle instead of passing the view through, so the tool does not receive shortened data without an error.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR makes prompt arguments one checked JSON object.

  • Prompts check one declared JSON object before their Lua sections run.
  • The Harness no longer stages a prompt’s declared input file.
  • Lua table views now measure and iterate like their backing data.
  • Task calls reject unknown options and report answered waits as unblocked.

Intent noted (vinniefalco): the removed APIs and wire fields are deliberate breaking changes. Chain overrides intentionally skip the prompt schema. Optional null counts as omitted, and missing required fields may take defaults.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Caller["Caller JSON object"] --> Check["Check args and fill defaults"]
  Check -->|Rejected| Error["First step: ArgsInvalid"]
  Check -->|Accepted| Args["Read-only args"]
  Args --> Section["Lua and prose"]
  Section --> Child["call or tasks.spawn"]
  Child --> Override["Own args, or inherited args"]
Loading

Reviews (1) · Last reviewed commit: "Refuse a `var` write and a task option t..." · Reviewed by Greptile

Comment on lines +211 to +212
if self.path.len() >= PLAIN_DEPTH_LIMIT || self.path.contains(&pointer) {
return Ok(None);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Cycles become empty objects

PlainWalk::value leaves a view unchanged when its hidden table is already on the current path. For example, local data = {}; local view = setmetatable({}, { __index = data, __metatable = 'view' }); data.self = view passed to tools.call becomes {"self":{}}. The copied field still holds the empty stand-in, which from_value reads as {}.

Refuse the cycle instead of passing the view through, so the tool does not receive shortened data without an error.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/runvil-internal/lua/src/proxy.rs
Line: 211-212

Comment:
**Cycles become empty objects**

`PlainWalk::value` leaves a view unchanged when its hidden table is already on the current path. For example, `local data = {}; local view = setmetatable({}, { __index = data, __metatable = 'view' }); data.self = view` passed to `tools.call` becomes `{"self":{}}`. The copied field still holds the empty stand-in, which `from_value` reads as `{}`.

Refuse the cycle instead of passing the view through, so the tool does not receive shortened data without an error.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@vinniefalco
vinniefalco merged commit 0d93888 into cppalliance:master Nov 14, 2026
13 checks passed
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