feat(agent): observe actions before dispatch

Merged
BH
3d ago
+266-7

What and why

  • Add optional agent.act({ onBeforeAction }) so an observer can capture the action's starting screen, rather than pair its label with the resulting screen.
  • Reuse the existing action queue, budgets, replay and observer-error handling. The new hook receives only index and source; existing onAction supplies the completed action's redacted description. No target objects, geometry or engine internals are exposed.
  • No automatic captures, per-action after-images, highlights, new executor or model loop. Existing completion callbacks, cache verification and secret/model-pixel policies stay unchanged. A failed dispatch can have a before-capture without a completion event.
const before = new Map<number, string>();
await agent.act('increment the counter twice', {
  onBeforeAction: async ({ index }) => {
    before.set(index, await app.screenshot(`before ${index}`));
  },
  onAction: ({ index, summary }) => {
    console.log(summary, before.get(index));
    before.delete(index);
  },
});

The observer runs once before dispatch, not once per internal relocation or low-level operation of a compound action. Its time counts toward the step deadline. A throw prevents dispatch and stops the step. Keep image association scoped to this act call; indices are not global IDs.

Issue

Refs https://github.com/tester-army/e2e/issues/1384. This is independent of the visual-judge policy proposal. Viewer adoption remains a consumer change; existing after-only records cannot be backfilled into genuine before-images.

Workflow

flowchart LR
  A[Existing queued action and budget] --> B[Optional before observer]
  B --> C[Existing action dispatch]
  B -- observer failure --> F[Stop without dispatch]
  C -- completed --> D[Existing onAction metadata]
  C -- failed --> E[Native failure handling]
  D --> G[Label the saved starting image]

Verified

Ran it locally: yes

  • Built CLI against the public counter fixture in real Chromium, with the built-in agent loop and a scripted model. Native verification checks counter states before and after actions. No live provider or device was used.
Built CLI scenariomainthis branch
onBeforeAction optionINVALID_ARGUMENTPassed
Cold executionNo before hookTwo before-images showing counter 0, then 1; 3 scripted model calls; native cache saved
Warm replayNo before hookTwo before-images showing 0, then 1; 2 replayed actions; 0 model calls; cache kept
  • Current-head CLI cold and warm runs passed on 876f3594ff94051142cd902b9e05dbd716e8d98f. Cold run 01a1761d-0c35-7bba-9e32-d45c6b789470; images 001-before_1_model.png and 002-before_2_model.png. Original artifact hashes were verified. No extra after-image was taken.
  • 3,039 core unit tests passed, one skipped. Focused observer, validation and replay coverage: 60 passed, including built-in execution, replay, observer failure before mutation, preserved cache, no in-hook cache confirmation, failed dispatches and mutating/read-only tools.
  • pnpm check passed from a clean temporary checkout carrying the identical patch. Public declarations and compile-time API checks passed. Full local integration/benchmark/device suites were not run; Cubic review completed on head 876f3594ff94051142cd902b9e05dbd716e8d98f: no issues found across all 12 files, with no unresolved threads. No code changes were needed from that review. All four CI gates pass on that head: spec, deterministic web benchmark, mobile and docs. Human review remains outstanding; the PR stays draft. The diff was self-reviewed against the repository checklist; no independent local-review claim.

Original starting-frame evidence

Public fixture from packages/e2e/tests/helpers/fixture-app.ts, rendered by the built web engine at commit 876f3594. The images were verified as original runner artifacts, including the engine's normal secure-field mask, with no local edits or overlays. They are consecutive before captures, not a before/after pair. The first is embedded below; upload of the second was blocked by upstream repository attachment permissions.

Before action 1: counter 0. SHA-256 62d60510d4304fd5ca1de8c3609fb9d947b09d05af88b49bc9e4a6af11ddae54.

Original before-action 1 capture showing counter 0

Before action 2: counter 1. SHA-256 4b998c3f805f7f61710796ec044f73cf471c5580575548c7027e51a5ee70c33e.

The second original was checked locally; it is not embedded. Native pre-action verification and replay checks above establish the observed counter sequence.

Checklist

  • Patch changeset for e2e.
  • Optional API; existing behavior unchanged when absent.
  • Docs, consumer skill and generated plugin copy updated.
  • Emitted declaration and compile-time API assertions checked; no engine SPI, report/session schema or cache-key changes.
  • Current-head Cubic review completed with no findings.
  • All four current-head CI gates pass.
  • Human review; preserve draft status.