Skip to content

docs(batch): name the step shape and the accepted commands in help batch and in its refusals - #2067

Open
NicolasBataille wants to merge 3 commits into
callstack:mainfrom
NicolasBataille:docs/2062-batch-step-shape
Open

docs(batch): name the step shape and the accepted commands in help batch and in its refusals#2067
NicolasBataille wants to merge 3 commits into
callstack:mainfrom
NicolasBataille:docs/2062-batch-step-shape

Conversation

@NicolasBataille

Copy link
Copy Markdown

Closes #2062

Summary

The premise of the issue does not hold, and that is the finding. press, click, fill,
longpress, scroll and back all carry batchable: true in the command-descriptor registry —
including at 0.20.10, which is the version the report was filed against (git show v0.20.10:src/core/command-descriptor/registry.ts). Mutating UI verbs were never excluded from
batch. The exclusions are batch/replay (which never nest) and the session, daemon, connection
and host-tooling commands, none of which is a UI verb.

What actually blocked the reporter is that batch accepts exactly one step shape and nothing said
so
. help batch was a usage line, one sentence and the flags. Every refusal named only what was
wrong:

input before
'["press @e12"]' Invalid batch step 1.
'[{"command":"press","args":["@e12"]}]' Batch step 1 has unknown field(s): args.
'[{"command":"session","input":{}}]' ... is not available through command batch: session

None of those says what a step looks like or where the boundary is, so "the verb is not batchable"
is a reasonable conclusion to reach from them. Per the issue's own second branch, this PR does not
touch the allowlist — it states it.

  • help batch now documents the step shape, the serial semantics, and renders the accepted
    commands from the registry's batchable trait
    , so the listing cannot drift from the runtime
    allowlist. It also says explicitly that the mutating UI verbs are included, which is the sentence
    the reporter needed.
  • The step-shape refusals (non-object step, unknown field, non-object input) share one hint
    naming {"command":"<name>","input":{...}}. It lives in packages/contracts/src/batch-contract.ts
    next to the two checks that raise it, so the CLI, the command metadata and the daemon projection
    all get the same sentence from one place.
  • The non-batchable-command refusal points at that listing and names which families are
    excluded and why.
  • assertAllowedKeys takes an optional hint, so the two batch call sites attach theirs without a
    second unknown-key check being written.
  • --steps reads JSON array of {"command","input"} steps, so the shape is visible at flag level too.

After:

Error (INVALID_ARGS): Batch step 1 has unknown field(s): args.
Hint: Each batch step is {"command":"<name>","input":{...}} — the same input object that command
      takes on its own. There is no positional step form: run agent-device help <command> for its
      arguments, and agent-device help batch for the commands batch accepts.

batch's description gains a trailing period so the help body reads as prose once cliDetail is
appended; server.json carries no tool descriptions, and pnpm check:mcp-metadata is green.

Tests

In src/commands/batch/cli.test.ts and src/cli-schema/cli-help-topics.test.ts; the four
assertion tests were observed red against the pre-fix code:

  • a non-object step, an args step, and a non-batchable command each name the missing half;
  • help batch documents the shape and lists press/click/fill/longpress/scroll/back;
  • and one test that passes today on purpose: two mutating verbs reach daemon dispatch through
    batch. It is the regression pin for the premise above — if the allowlist is ever narrowed, that
    has to be a deliberate registry change rather than a silent one.

pnpm check:quick, pnpm test:unit (8092 passed), pnpm check:command-docs and
pnpm check:mcp-metadata are green.

Live check

iOS 26.5 simulator (iPhone 17), DemoApp, CLI from this clone — one batch request, three steps:

$ agent-device batch --steps '[{"command":"press","input":{"target":{"kind":"selector","selector":"id=\"tabBar.form\""}}},
                               {"command":"fill","input":{"target":{"kind":"selector","selector":"id=\"form.textField\""},"text":"batched"}},
                               {"command":"snapshot","input":{"interactiveOnly":true}}]'
1 press    ok  Tapped id="tabBar.form" (287, 822)
2 fill     ok  Filled 7 chars
3 snapshot ok

That is the observe→act→verify sequence the issue says cannot be amortised, running today. Session
closed and daemon stopped afterwards.

Worth noting for the issue thread: with @ref steps, step 2 fails ref_frame_expired because
step 1's press invalidates the ref frame — correct and documented, but it means a multi-step batch
wants selectors, not refs. That is a real ergonomic limit on batching mutations and may deserve its
own issue.

What a maintainer might push back on

  • The full command list in help batch is long (48 names, one wrapped paragraph). The
    alternative is listing the ~20 exclusions instead, which is shorter but inverts the question an
    agent is asking. Rendering either from the registry is the part that matters.
  • cliDetail is one paragraph because helpBody joins with a space. Four sentences is at the
    upper end of what that format carries well.
  • The optional hint on assertAllowedKeys is a generic helper gaining a parameter for two
    callers. The alternative — a batch-local unknown-key check — duplicates the check itself.
  • This closes the issue without changing behaviour. If the maintainers would rather also record
    why each excluded command is excluded (the registry declares the trait but no rationale), that
    is a separate, larger pass over registry.ts.

…d refusals

`batch` accepts one step shape and `help batch` documented none of it: the
usage line, one sentence, and the flags. Every refusal named only what was
wrong. A caller reaching for `press` through `batch` therefore saw
"Invalid batch step 1." for `["press @E12"]` and "unknown field(s): args" for
`{"command":"press","args":[...]}`, and reasonably concluded the mutating verbs
were excluded (callstack#2062).

They are not, and never were: `press`, `click`, `fill`, `longpress`, `scroll`
and `back` all carry `batchable: true` in the command-descriptor registry,
including at 0.20.10. The exclusions are `batch`/`replay` (which never nest) and
the session/daemon/connection/host-tooling commands. Nothing about the
allowlist changes here; what changes is that it is stated.

- `help batch` documents the step shape, serial semantics, and RENDERS the
  accepted commands from the registry's `batchable` trait, so the listing cannot
  drift from the runtime allowlist.
- The step-shape refusals (non-object step, unknown field, non-object input)
  share one hint naming `{"command":"<name>","input":{...}}`, owned by
  `batch-contract.ts` next to the checks that raise them.
- The non-batchable-command refusal points at that listing and says which
  families are excluded and why.
- `assertAllowedKeys` takes an optional hint so the batch call sites can attach
  theirs without a second unknown-key check.

Closes callstack#2062
@thymikee

Copy link
Copy Markdown
Member

[P1] Make the batch guidance truthful and owned before calling this discoverability fix complete. The new hint says the structured input is “the same input object that command takes on its own” and directs callers to help <command>, but command help exposes CLI positionals/flags—not metadata/MCP/Node object keys such as target: {kind, selector}, text, or interactiveOnly; the PR’s own live example requires those undiscoverable keys. Point to or render the actual descriptor-backed structured schema/examples and cover a representative press/fill/snapshot step from rendered help. Also fix the existing invalid/stale batch guidance this audit missed: help commands says batch ./steps.json although positional input is rejected, and help scripting says legacy positionals/flags steps still work even though #2046 removed them. Use batch --steps-file ./steps.json and state only the supported shape. Finally, keep the contract-layer hint surface-neutral: embedding agent-device help ... in @agent-device/contracts leaks CLI recovery text into Node/MCP validation. The derived accepted-command roster itself is sound. Exact-head CI is absent; do not apply ready-for-human.

…and keep contracts surface-neutral

`help batch` now prints runnable snapshot/press/fill steps carrying the real
structured field names (`target: {kind, ref}`, `text`, `interactiveOnly`), which
no `help <command>` text states, and says so instead of pointing at command help
for them. `cli-help-examples.test.ts` reads those steps back out of the rendered
help and runs each `input` through its own command's `readInput`, so a renamed
field fails there rather than shipping guidance nobody can run.

Fixes the stale batch guidance the audit missed: `help workflow` named
`batch ./steps.json`, which positional input rejects, and `help scripting` still
weighed the removed positionals/flags shape against the accepted one.

`BATCH_STEP_SHAPE_HINT` in `@agent-device/contracts` describes the shape only;
`readBatchStepRecord`/`readBatchStepInputObject` take the hint as a parameter so
the CLI attaches its own `agent-device help batch` recovery step while the Node
client and MCP tools keep the surface-neutral one.
…e refusal

The example-validation test accepted a step whose optional key the
reader silently dropped — readInput ignores unknown keys, so a renamed
settle or interactiveOnly kept the test green while help advertised a
step that does less than it claims. Every printed key must now survive
into the parsed input.

The removed positionals/flags refusal carries the CLI shape hint like
its three sibling refusals.
@thymikee

Copy link
Copy Markdown
Member

Addressed the P1 in b27a2a4 and 66feec4 (maintainer commits on this branch):

  • Truthful, drift-proof examples. help batch now prints three real structured steps — snapshot {interactiveOnly}, press {target:{kind:'ref'}, settle}, fill {target:{kind:'selector'}, text} — typed as BatchCommandStep literals, and the prose states the actual CLI↔structured mapping (positionals become named fields, flags become camelCase keys) instead of pointing at help <command>, which only speaks CLI spelling. A test extracts the examples from the rendered help, runs them through readCliBatchStepsJson and each command's own readInput, and — after an adversarial pass showed readInput ignores unknown keys — asserts every printed key survives into the parsed input, so a renamed settle/interactiveOnly fails the build rather than shipping an example that silently does less.
  • Stale guidance fixed where it actually lives: the workflow topic's batch ./steps.json is now batch --steps-file ./steps.json (help batch), and help scripting no longer claims the removed positionals/flags steps still work — it states the whole accepted shape. Repo-wide sweep found no other stale sites (website docs already correct).
  • Surface-neutral contracts: BATCH_STEP_SHAPE_HINT in @agent-device/contracts describes only the shape; the CLI call sites append the run agent-device help batch recovery via the existing optional-hint parameters. A contracts test bans terminal vocabulary in the shared hint. The removed-shape refusal now carries the same CLI hint as its three siblings. (BATCH_AVAILABLE_COMMANDS_HINT in src/core keeps its wording deliberately: it is app code, and the MCP help tool serves the CLI help text by design.)
  • The batchable-derived roster is untouched, per the review.

Validation: unit-core full suite green (7713+ tests at implementation time, 438 for the batch/help/contracts scope after the final commit), output-economy green, check:quick clean, rendered help batch/help scripting/help workflow inspected, and the CLI refusal verified live.

Pre-existing, out of scope: batch --help errors instead of printing help (the step-source check runs before help dispatch; since 4c02b6a) — worth its own issue.

@thymikee

Copy link
Copy Markdown
Member

Re-review at 66feec4863c8ceb9bff640ce46553b0703425190: the previously posted help-contract P1 is resolved. Structured batch examples now use the real schema, are parsed through the batch envelope and command readInput, and CLI-only recovery wording remains in the CLI owner rather than contracts. No new code finding. Not labeling ready-for-human yet because all seven exact-head workflows completed action_required with zero jobs, so required CI evidence did not run.

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.

help batch and batch errors do not document the step shape or the batchable command set (press/fill *are* batchable)

2 participants