Skip to content

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

Merged
thymikee merged 4 commits into
callstack:mainfrom
NicolasBataille:docs/2062-batch-step-shape
Aug 27, 2026
Merged

docs(batch): name the step shape and the accepted commands in help batch and in its refusals#2067
thymikee merged 4 commits into
callstack:mainfrom
NicolasBataille:docs/2062-batch-step-shape

Conversation

@NicolasBataille

Copy link
Copy Markdown
Contributor

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.

@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.

@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.

@thymikee

Copy link
Copy Markdown
Member

P2: keep command-availability recovery surface-aware. readStructuredBatchCommandName now unconditionally emits Run agent-device help batch, but the same reader backs createBatchCommandMetadata and therefore the MCP/Node batch surface. An MCP batch call rejected for an unavailable nested command receives a terminal-only, unrunnable recovery step—the cross-surface distinction this PR correctly preserves for the step-shape hint. Keep the shared/default availability hint surface-neutral, attach terminal recovery only at CLI admission/projection, and add a planted MCP/shared regression. Until then this is not ready.

thymikee added a commit to NicolasBataille/agent-device that referenced this pull request Aug 27, 2026
…es its recovery

readStructuredBatchCommandName emitted 'Run agent-device help batch'
unconditionally, and the same reader backs the MCP/Node batch metadata
— an MCP caller got a terminal-only, unrunnable recovery step. The
shared default now states the exclusion boundary itself with no
terminal vocabulary (MCP/Node read the accepted commands off the step
schema's command enum), and the CLI admission appends the help pointer
via the same optional-hint parameter the shape hint uses.

Found while fixing it: hint strings are redaction-capped at 400
characters, so enumerating the derived roster inline truncates the
hint — recovery pointer and all. The regression pins neutrality AND
that the hint survives the cap whole.

Addresses the P2 review on callstack#2067.
@thymikee

Copy link
Copy Markdown
Member

P2 addressed in db6615a. BATCH_AVAILABLE_COMMANDS_HINT is now surface-neutral — it states the exclusion boundary with no terminal vocabulary, and MCP/Node read the accepted commands off the step schema's command enum, which is the machine-readable roster. The CLI admission appends the help batch pointer through the same optional-hint parameter the shape hint uses. Planted regressions: the metadata refusal is checked for neutrality (no agent-device , no flags) and the CLI refusal for the help recovery.

One discovery from doing it: hint strings are redaction-capped at 400 characters, so enumerating the derived roster inline (my first attempt) truncated the hint — recovery pointer and all. The regression also pins that the hint survives the cap whole.

NicolasBataille and others added 4 commits August 27, 2026 11:48
…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
…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.
…es its recovery

readStructuredBatchCommandName emitted 'Run agent-device help batch'
unconditionally, and the same reader backs the MCP/Node batch metadata
— an MCP caller got a terminal-only, unrunnable recovery step. The
shared default now states the exclusion boundary itself with no
terminal vocabulary (MCP/Node read the accepted commands off the step
schema's command enum), and the CLI admission appends the help pointer
via the same optional-hint parameter the shape hint uses.

Found while fixing it: hint strings are redaction-capped at 400
characters, so enumerating the derived roster inline truncates the
hint — recovery pointer and all. The regression pins neutrality AND
that the hint survives the cap whole.

Addresses the P2 review on callstack#2067.
@thymikee
thymikee force-pushed the docs/2062-batch-step-shape branch from db6615a to f1129cc Compare August 27, 2026 09:51
@thymikee

Copy link
Copy Markdown
Member

Rebased onto current main (f1129cc): #2065/#2066/#2068 merged underneath, one additive conflict in cli-help-topics.test.ts (both new help tests kept). Line-level check confirms the branch now carries only this PR's batch/help changes over main. Full unit suite green on the rebased head (1071 files / 8152 tests), plus check:quick, fallow, and output-economy.

@thymikee

Copy link
Copy Markdown
Member

Re-reviewed the changed head f1129cc37958537bf11b84927e48ce27091f03fc. The prior surface-leak finding is resolved: the shared batch-command reader now keeps a surface-neutral boundary hint, while only CLI parsing appends the terminal-specific agent-device help batch recovery. The direct MCP/Node regression confirms no terminal spelling leaks and preserves the schema enum as the machine-readable command roster. I found no new code issue in this delta. Typecheck, package, lint, repository guards, and provenance are green; coverage/integration are still pending. The Android smoke timeout appears unrelated to this batch/docs delta but remains an unclassified live-device failure, so I am not marking the PR ready yet.

@thymikee

Copy link
Copy Markdown
Member

The reviewed code head is unchanged and the prior batch-surface finding remains resolved. The previously unclassified Android smoke timeout has now rerun successfully; iOS, macOS, Linux, both coverage shards, integration, type/package, lint, guards, and provenance are also green. Bundle Size and the cache matrix entry are expected skips for this docs/batch change. No code or evidence blocker remains; this is merge-ready.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 27, 2026
@thymikee
thymikee merged commit b44f882 into callstack:main Aug 27, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

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