Skip to content

Commit b3b9ff5

Browse files
authored
docs: propose active-session script publication (#1347)
* docs: propose active-session replay publication * docs: address active-session replay review * docs: align active-session publication on save-script * docs: qualify destination guard identity * docs: close active-session publication boundaries
1 parent 1f042c3 commit b3b9ff5

3 files changed

Lines changed: 229 additions & 0 deletions

File tree

CONTEXT.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
- Target: selected automation destination, such as mobile, tv, or desktop.
2929
- Modality: broad supported device family, such as mobile, tv, or desktop.
3030
- Session: daemon-owned state for a selected target and opened app or surface.
31+
- Script recording: opt-in session mode armed before actions so a persisted `.ad` can carry portable
32+
action inputs and recording-time target identity evidence. It is distinct from screen/video recording.
33+
- Open-to-destination script: self-contained `.ad` script with exactly one initial `open`, a destination
34+
guard after its last app-state mutation, no `close`, and an app session left active for subsequent work.
35+
Avoid: replay (artifact noun), fragment (reserved for lifecycle-free composition), partial script.
36+
- Destination guard: portable selector-targeted `wait` near the end of an open-to-destination script that
37+
confirms a landmark on the ready destination screen before replay hands the live session to its caller.
3138
- Recording backend: daemon-internal module interface selected per recording target that owns platform recording validation, output path policy, start/stop execution, and record-only cleanup below the daemon recording lifecycle.
3239
- Device lease: logical remote ownership of one selected device for a
3340
tenant/run/client and lease provider, separate from platform helper process
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# ADR 0016: Active-Session Script Publication
2+
3+
## Status
4+
5+
Proposed
6+
7+
## Context
8+
9+
The replay repair study found re-recording a drifted journey cheaper than repairing it in two of the
10+
three measured drift classes. The immediate reusable unit is an **open-to-destination script**: a
11+
self-contained `.ad` script that opens the app, performs the complete journey to screen X, verifies a
12+
destination landmark, and leaves the app session active there so an agent can continue with new work.
13+
14+
Ordinary script recording currently combines two concerns. `open --save-script[=<path>]` arms
15+
recording before the first interaction so selector chains and ADR 0012 `target-v1` identity evidence
16+
are captured at action time. `close` publishes the accumulated script, records a terminal `close`, and
17+
tears down the session. That artifact is suitable as a closed test flow, but not as a live starting
18+
state.
19+
20+
Publishing only at the end of an ordinary unarmed session is insufficient. Session history retains the
21+
commands, but the resolved target tree needed for `target-v1` evidence is deliberately discarded after
22+
each action. Late publication can serialize selector text; it cannot reconstruct which element was
23+
actually acted on. Making identity capture unconditional for every session would change normal
24+
interaction execution: recording disables direct selector fast paths when a capture-backed route is
25+
required for evidence. That performance and data-retention change has not been measured.
26+
27+
Issue [#1346](https://github.com/callstack/agent-device/issues/1346) and the throwaway
28+
[`session-save-script` prototype](https://github.com/callstack/agent-device/tree/agent/prototype-session-save-replay/scripts/prototypes/session-save-script)
29+
record the motivating workflow and API trial. Composable lifecycle-free fragments remain a separate
30+
decision in [#1336](https://github.com/callstack/agent-device/issues/1336).
31+
32+
## Decision
33+
34+
Add an explicit publication action for an already-armed ordinary script recording:
35+
36+
```sh
37+
agent-device open com.example.app --relaunch --save-script=screen-x.ad
38+
# perform the complete journey to screen X
39+
agent-device wait 'role="heading" label="Screen X"'
40+
agent-device session save-script
41+
```
42+
43+
`session save-script [path] [--force]` publishes the current ordinary script recording without
44+
closing the app or deleting the session. An explicit `path` retargets the recording using the existing
45+
target/force authorization rules. Without it, publication uses the path armed by `open --save-script`
46+
or the existing generated default.
47+
48+
### Recording lifecycle
49+
50+
An ordinary recording eligible for active-session publication has three states:
51+
52+
- **ARMED**: established only when a new session's initial successful
53+
`open --save-script[=<path>]` is recorded as action zero. The session records portable action inputs
54+
and fresh target identity evidence.
55+
- **ABORTED**: reached when another plain `open` succeeds while ARMED. The app operation may continue,
56+
but the recording is no longer a single-open bootstrap and close-time publication is disarmed. The
57+
successful `open` response warns that a fresh session is required to author another script.
58+
- **PUBLISHED**: reached only after `session save-script` atomically commits the complete history from
59+
the sole recorded `open` through the current action. The session remains active at the destination,
60+
but close-time script publication is disarmed.
61+
62+
A filesystem or target-collision failure leaves the recording ARMED, including its path and same-target
63+
`--force` authorization, so the caller can correct the target or permissions and retry. ABORTED and
64+
PUBLISHED are terminal until the session is destroyed. `session save-script` in either state fails
65+
loudly and never writes.
66+
67+
Every existing arming entry point respects terminality. `open --save-script` on any existing session —
68+
unarmed, ARMED, ABORTED, or PUBLISHED — is rejected before app dispatch; a plain later `open` is allowed,
69+
causing ARMED to become ABORTED while leaving ABORTED/PUBLISHED unchanged.
70+
`close --save-script[=<path>]` in ABORTED or PUBLISHED is rejected before platform close or filesystem
71+
work, so the caller can retry with plain `close`. Plain `close` tears down ABORTED/PUBLISHED without
72+
writing; closing an unpublished ARMED recording retains the existing close-time publication behavior. A
73+
fresh session is the only re-arming boundary.
74+
75+
This lifecycle is distinct from ADR 0012's repair transaction. `session save-script` rejects a session
76+
with `saveScriptBoundary` set and directs the caller to finish or abort the repair through its existing
77+
`replay --from` and teardown commit protocol. Active-session publication never marks a repair COMPLETE,
78+
commits a healed slice, writes `# agent-device:heal-complete`, or changes repair tombstone semantics.
79+
80+
### Destination readiness and replay handoff
81+
82+
The destination is an authored postcondition, not the last navigation action. Before publication, the
83+
recorded suffix after the last mutating action must contain a `wait` whose target is a portable selector
84+
or selector chain identifying a landmark on the ready destination screen. A duration wait, `wait stable`,
85+
or `wait @ref` does not qualify, though `wait stable` may follow the landmark wait. The publisher validates
86+
the serialized guard before filesystem work and never relies on repair-only bare-ref rejection.
87+
`session save-script` refuses publication without this **destination guard** and tells the author to
88+
record one. V1 does not infer a screen identity from a snapshot or synthesize an implicit guard.
89+
90+
The initial guard is selector-level. `wait` does not yet carry recorded-landmark identity through its
91+
polling resolution, so the guard proves that an element matching its selector exists, not that it is the
92+
same landmark element observed while authoring. Authors must choose a selective destination-specific
93+
landmark; a reshuffled screen containing the same weak label elsewhere can false-pass.
94+
[#1349](https://github.com/callstack/agent-device/issues/1349) owns the identity design for waits and
95+
remaining read-only steps. It must preserve polling: ADR 0012's current pre-action `target-v1`
96+
verification cannot be attached to a wait unchanged because a not-yet-present landmark is the expected
97+
starting condition. Any identity check for a wait happens after its selector resolves, before the wait
98+
reports success, or uses a distinct guard-specific mechanism.
99+
100+
The phrase "last mutating action" is derived from a request-sensitive recording-effect trait on the
101+
central `CommandDescriptor`, required for every command that records session actions and guarded by a
102+
completeness test. It is not a publisher-local command-name set. The trait distinguishes app-state
103+
mutation from observation for subcommands such as read-only versus mutating `find`, `keyboard`, and
104+
`alert` actions; the existing conservative `refFrameEffect: may-invalidate` classification is not precise
105+
enough for this boundary.
106+
107+
On consumption, a script without `close` preserves the existing replay behavior: the named session stays
108+
active and the successful `ReplayCommandResult` returns its `session` id. The caller binds subsequent
109+
commands to that returned id. Replay reports success only after the destination guard completes; the
110+
absence of `close` changes neither action dispatch nor the success response shape.
111+
112+
### Sensitive inputs
113+
114+
Executable `.ad` artifacts serialize action inputs literally. A recorded `fill` therefore writes its
115+
text to the published file; diagnostic and event-log redaction cannot protect an input that the replay
116+
engine must later execute. V1 does not claim secret-bearing login flows are safe to publish.
117+
118+
Native `.ad` replay already supports late-bound `${VAR}` values, but ordinary recording cannot yet
119+
execute with a real value while publishing only its placeholder. That safe-authoring capability is
120+
tracked in [#1348](https://github.com/callstack/agent-device/issues/1348). Until it ships, authors must not
121+
record a journey that enters a secret: use pre-authenticated test state or deliberately non-secret fixture
122+
credentials that are safe to persist. CLI help must state this warning next to the authoring workflow.
123+
124+
### Artifact contract
125+
126+
The published `.ad`:
127+
128+
- contains exactly one recorded `open` as its first action and every recordable action through the
129+
publication request;
130+
- contains a portable selector/selector-chain destination guard after its last descriptor-classified
131+
mutating action;
132+
- does not append or serialize `session save-script` or `close`;
133+
- uses the ordinary session context header, selector-chain optimization, and canonical `target-v1`
134+
annotations captured while ARMED;
135+
- fails loudly rather than emitting an unresolved session-local `@ref` or dropping target evidence that
136+
ADR 0012 requires for an element-targeting recorded action; and
137+
- uses the existing same-directory atomic publication primitive, refusing every existing target unless
138+
`--force` authorizes atomic replacement.
139+
140+
The success response identifies the final path and session and reports the number of serialized actions.
141+
The command must fail before writing when there is no active session, recording was not armed, the
142+
recording is ABORTED or PUBLISHED, the history does not contain exactly one initial `open`, no portable
143+
destination guard exists, or a repair transaction owns the session. Every failure explains the recovery
144+
action; none degrades to `{ written: false }` success.
145+
146+
### Surface and naming
147+
148+
V1 extends the existing `session` command and typed session client surface. It does not introduce
149+
`script start/stop`, marks, or a second replay engine. CLI help makes the two phases explicit: the
150+
existing `--save-script` flag arms evidence capture, while `session save-script` publishes without
151+
teardown.
152+
153+
This ADR does not rename or deprecate `--save-script`. The flag and session action name the same persisted
154+
artifact: `open --save-script` configures the armed recording's eventual target, while
155+
`session save-script` requests publication now. `save-replay` is rejected because replay is the act of
156+
executing that script, not the artifact being saved.
157+
158+
## Consequences
159+
160+
- Agents can record onboarding or deep navigation as one self-contained starting state, replay it from
161+
scratch, and continue from the resulting live session.
162+
- The workflow has two explicit moments because evidence must be armed before the first target action and
163+
the destination is known only when the caller publishes.
164+
- Normal unarmed interactions keep their current fast paths and retention behavior.
165+
- A successful active-session publication cannot collide with a later close-time auto-save.
166+
- A second successful open abandons the in-flight artifact instead of silently publishing a multi-open
167+
bootstrap; authoring resumes only in a fresh session.
168+
- Intermediate lifecycle-free fragments, entry guards, include semantics, composed digests, and shared
169+
fragment pinning remain entirely under #1336.
170+
- Secret-bearing authoring remains unsafe until #1348; the initial workflow is limited to journeys that
171+
do not enter secrets. Arbitrary history ranges remain out of scope.
172+
173+
## Alternatives Considered
174+
175+
- **Save any session history at the end:** rejected because target identity evidence cannot be
176+
reconstructed after the interaction and the resulting artifact would undercut ADR 0012's provenance
177+
model.
178+
- **Capture full target evidence in every session:** rejected until its direct-path latency, capture
179+
count, memory, and event-log costs are measured. It would alter ordinary interaction behavior to make
180+
one authoring command shorter.
181+
- **`close --no-close` or `--save-script --no-close`:** rejected because a command named for teardown
182+
would conditionally preserve the session and because it would not solve late arming.
183+
- **General `script start/stop` or history marks:** rejected because the accepted v1 boundary is exactly
184+
one recorded `open` through one destination. Arbitrary slices require entry-state semantics and belong
185+
with fragment design.
186+
- **`replay save`:** rejected because `replay <path>` consumes an artifact while publication consumes a
187+
live session; the session owns the source data and lifecycle.
188+
- **Infer a destination fingerprint at publication:** rejected for v1 because screen identity and
189+
readiness are app semantics. A caller-authored target wait is explicit, already recordable, and fails
190+
at the correct point during cold replay.
191+
192+
## Validation Required for Implementation
193+
194+
- An unarmed session refuses publication before filesystem work and names `open --save-script` as the
195+
recovery.
196+
- An armed session without a destination guard refuses publication before filesystem work and names a
197+
selector-targeted `wait` as the recovery; `wait @ref`, duration waits, and `wait stable` are covered
198+
refusal cases.
199+
- A second successful plain `open` transitions ARMED to ABORTED and disables all publication, while
200+
`open --save-script` on an existing session is rejected before app dispatch.
201+
- An armed session publishes `open` plus target-annotated actions without `close`, returns the final path,
202+
remains active, and can continue accepting commands.
203+
- The artifact replays from a cold start, completes its destination guard, returns the live session id,
204+
and accepts a subsequent command on that session.
205+
- Every action in ADR 0012's existing target-binding command set has canonical identity evidence and no
206+
unresolved `@ref` reaches disk; the destination guard remains selector-level until #1349 lands.
207+
- Existing-target refusal preserves the original bytes; `--force` replaces atomically; a failed publish
208+
remains retryable.
209+
- After PUBLISHED, later ordinary actions remain usable, repeated `session save-script` fails, plain
210+
`open --relaunch` cannot re-arm, and `open --save-script` is rejected before app dispatch.
211+
- In ABORTED/PUBLISHED, `close --save-script[=<other>]` is rejected before platform close and plain
212+
`close` tears down without writing; closing an unpublished ARMED recording preserves current
213+
close-time publication behavior.
214+
- Descriptor completeness tests classify every recordable request's mutation effect, including
215+
request-sensitive read-only/mutating subcommands, and destination-guard ordering consumes only that
216+
trait.
217+
- Repair-armed sessions refuse this action without changing repair state.
218+
- CLI help warns that literal `fill` inputs are persisted and tells authors not to record secret-bearing
219+
journeys until #1348's parameterized-input mechanism is available.
220+
- Provider-backed integration scenarios cover the public daemon route, and live iOS and Android runs
221+
prove the saved artifact and post-save session behavior on real backends.

docs/adr/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
| [0013 Unified Gesture Plans](0013-unified-gesture-plans.md) | gesture API/routing, contact topology, multi-touch geometry, native pointer injection, two-finger pan |
1818
| [0014 Session Ref-Frame Lifetime](0014-session-ref-frame-lifetime.md) | ref authorization epochs, complete/partial issuance, pre-side-effect expiration, replay/batch compatibility, and cross-platform stale-mutation policy |
1919
| [0015 Direct Maestro Compatibility Engine](0015-direct-maestro-engine.md) | Maestro YAML parsing/execution, compatibility observation policy, conformance, performance gates, gesture integration |
20+
| [0016 Active-Session Script Publication](0016-active-session-script-publication.md) | publishing an armed open-to-destination `.ad` script without closing its live session |
2021

2122
ADRs record *why*; the registries and gates they describe are the living source of truth — when
2223
prose and a registry disagree, the registry wins and the ADR needs a follow-up.

0 commit comments

Comments
 (0)