feat(agent): observe actions before dispatch
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
indexandsource; existingonActionsupplies 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 scenario | main | this branch |
|---|---|---|
onBeforeAction option | INVALID_ARGUMENT | Passed |
| Cold execution | No before hook | Two before-images showing counter 0, then 1; 3 scripted model calls; native cache saved |
| Warm replay | No before hook | Two 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 run01a1761d-0c35-7bba-9e32-d45c6b789470; images001-before_1_model.pngand002-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 checkpassed 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 head876f3594ff94051142cd902b9e05dbd716e8d98f: 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.
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.