Context
The operator's 2026-08-02 flagship-film brief (90–150s, heavy drag-and-drop) names undo/redo as a film beat — heavy drag-and-dock mutations, then reverting them live. Fable's lane map on #15252 (issuecomment-5153855472) scoped L3 = "undo/redo wiring — dashboard transaction machinery exists; workstation surface + a deterministic spec beat needed," the natural sibling of L2. @neo-kimi-phoebe claimed L2+L3 at 2026-08-01T23:15:25Z (A2A MESSAGE:c278901d-fdf1-4524-b275-c6ddfff83f24). Budget clock: the Fable family may go dark within 2–3 days — every artifact lands durably same-turn.
The Problem
Dock mutations never reach the transaction recorder — undo/redo of dock operations is unreachable through the Neural Link path today, full stop.
Live receipt (headed, 2026-08-02, workstation dockdemo ?demo=b; full detail in the intake-finding comment):
begin_transaction → opened: true
execute_dock_operation ×2 → applied: true (both mutations landed)
commit_transaction → committed: false, reason: "empty-transaction"
list_transactions → {committed: [], redo: []}Root cause (grep receipt): recordUndo — the capture hook feeding Neo.ai.TransactionService — is invoked only from src/ai/client/InstanceService.mjs (:141, :500, :1143), covering instance create / remove / set-properties. src/ai/client/DockService.mjs (executeDockOperation, capturePerspective, restorePerspective) has no capture hook. The machinery works; the dock mutation paths never record.
Additionally (original premise, still true): zero transaction wiring exists anywhere in apps/agentos/ — no demo surface drives begin/commit/undo/redo, so the film cannot show the beat even where recording works.
What exists (verified this session):
Neo.ai.TransactionService — src/ai/TransactionService.mjs:95 — per-session named transactions: begin / commit / abort, per-session undo & redo stacks (open → committed → undone, redo re-entry, commit clears the redo branch), audit labels via list_transactions over stackOf
- Reverse ops are data-not-code: JSON tool descriptors, re-dispatched and re-enforced as the current requester at undo time
- NL tools:
begin_transaction, commit_transaction, abort_transaction, undo, redo, list_transactions, save_transaction, replay_transaction
- Working capture-hook precedent:
src/ai/client/InstanceService.mjs (validated reverse-descriptor builders at :500/:1143, hook at :684)
The Architectural Reality
- The film beat: heavy drag-and-dock mutations on the workstation wrapped in one named transaction, then
undo restoring the prior dock state live — the "it can UNDO that?" moment
- Transactions are per App Worker session; the demo app boots on a SharedWorker, so cross-window mutations land on one heap — one transaction boundary can span windows
- For
execute_dock_operation the honest reverse is the pre-mutation dockZone.v1 document — the document IS the state; the reverse descriptor must re-apply it through the workspace's document-commit path (onWorkspaceDocumentChange / refresh projection), not masquerade as a synthetic forward op
- The deterministic drive path for film + spec is the NL tool chain, not ad-hoc UI clicking
The Fix
- Close the recording gap: add the
recordUndo capture hook to the app-worker DockService mutation paths — executeDockOperation first (the film's drag-and-dock beat); restorePerspective as a state-changing candidate (implementation decides, documented in the PR). Reverse = the pre-mutation document as a data-not-code descriptor, designed against DockZoneModel's apply path and the InstanceService builder precedent.
- Verify the live chain (headed + GPU receipts only):
begin_transaction → dock mutations → commit_transaction (ops captured) → undo → assert dock topology restored → redo → re-applied. Receipt on this ticket.
Wire the demo surface minimally — resolved BY DESIGN (2026-08-02): the deterministic film drive is the NL tool chain (the transaction stack is deliberately keyed on the Bridge-stamped (agentId, sessionId) writer pair — see InstanceService#beginTransaction). App-side transaction cues were rejected: an in-app cue would have to smuggle a pseudo-writer identity into the agent-scoped stack authority. The screenplay narrates via the existing caption machinery while its automation drives NL tools. Recorded in Avoided Traps.
- Spec beat: deterministic whitebox-e2e witness (sibling pattern: existing
test/playwright/e2e/agentos/*NL.spec.mjs) asserting undo restores the pre-transaction dock document and redo re-applies it.
- Handoff: filmable cue sequence posted as a comment on #15252 same-turn.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
src/ai/client/DockService.mjs executeDockOperation |
src/ai/TransactionService.mjs lifecycle + the base Neo.ai.client.Service#recordUndo contract |
Records a data-not-code reverse op into an open batch; auto-wraps as its own single-op committed transaction when no batch is open (mirroring the InstanceService contract — every un-batched dock mutation is independently undoable) |
n/a |
JSDoc |
spec + headed receipt |
DockZoneModel.applyDocument (new dockZone.v1 operation) |
DockZoneModel.commit() fail-closed normalize+validate |
Declared public agent capability: commits a caller-supplied candidate document through the shared commit path; validation errors fail closed with the live document untouched. Enters the advertised vocabulary mechanically (operations derives from the handler table → get_dock_topology) — this row is the deliberate declaration of that expansion, not a derivation side effect |
n/a (new) |
JSDoc on the static |
spec + headed receipt |
NL begin_transaction / commit_transaction / undo / redo |
src/ai/TransactionService.mjs:95 + NL tool schemas |
Verified live against dockdemo; spec-witnessed |
n/a (exists) |
tool schema |
headed receipt + spec |
NL save_transaction / replay_transaction |
same |
Verified live (archive + replay round-trip) |
n/a (exists) |
tool schema |
spec |
| Demo tour cue table |
DemoBWorkspace.mjs:1413 |
New transaction cues (wrap / undo / redo) |
NL-direct drive in spec |
tour script |
spec |
Decision Record impact
none — closes a recording gap in existing merged machinery (TransactionService); no ADR-amending surface.
Acceptance Criteria
Out of Scope
- TransactionService feature work (no lifecycle changes — the stack authority is sound)
- Recording hooks for non-dock mutation families beyond what the film beat needs (instance mutations already record)
- Undo semantics for non-dock mutations (data-store edits, forms)
- Cross-session transaction portability beyond what
save_transaction / replay_transaction already provide
- L2 perspectives wiring (sibling ticket
#16315, PR #16319)
Avoided Traps
- Re-implementing undo in the app layer instead of driving the existing TransactionService — the machinery exists; the gap is recording + wiring + witness, not new architecture
- Synthetic inverse operations as reverses (e.g. hand-computed
moveItem-back) — the document IS the state; the honest reverse re-applies the pre-mutation document
- App-side transaction cues (a
transaction-wrap / undo tour cue) — the stack authority is deliberately keyed on the Bridge-stamped (agentId, sessionId) writer pair; an in-app cue would smuggle a pseudo-writer into it. The screenplay's automation drives the NL tools with its own writer identity; captions narrate through the existing tour machinery
- Treating the whole-document reverse as cross-writer-safe — it is per-writer last-writer-wins: writer A's undo re-commits A's pre-mutation document, silently discarding any mutation writer B interleaved between A's capture and A's undo.
targetSubtreePath is audit metadata, never the enforcement path (TransactionService.mjs:89); nothing at undo time checks the subtree still matches capture-time. The bound is inherent to document-as-state (not a defect), the demo/film is single-writer so it cannot surface there, and it is named here + in buildDockReverse's JSDoc so no consumer inherits it as an unstated assumption
- Headless-only receipts: headless canvas is blank — only headed+GPU runs count (operator contract)
Related
- Parent epic: #15252 (this is the L3 leaf); sibling L2 leaf:
#16315 (PR #16319)
- Lane map: #15252 issuecomment-5153855472; claim: A2A 2026-08-01T23:15:25Z
Live latest-open sweep: checked latest 20 open issues at 2026-08-01T23:24Z; no equivalent found. A2A in-flight sweep (60 min): no competing claim on this scope. KB semantic sweep surfaced the TransactionService machinery and NL tool set, no open duplicate.
Origin Session ID: 14f1d6fa-235a-4101-88d4-c9490e3f7fd5
Retrieval Hint: "flagship film undo redo transaction dockdemo witness"
Context
The operator's 2026-08-02 flagship-film brief (90–150s, heavy drag-and-drop) names undo/redo as a film beat — heavy drag-and-dock mutations, then reverting them live. Fable's lane map on #15252 (issuecomment-5153855472) scoped L3 = "undo/redo wiring — dashboard transaction machinery exists; workstation surface + a deterministic spec beat needed," the natural sibling of L2. @neo-kimi-phoebe claimed L2+L3 at 2026-08-01T23:15:25Z (A2A
MESSAGE:c278901d-fdf1-4524-b275-c6ddfff83f24). Budget clock: the Fable family may go dark within 2–3 days — every artifact lands durably same-turn.The Problem
Dock mutations never reach the transaction recorder — undo/redo of dock operations is unreachable through the Neural Link path today, full stop.
Live receipt (headed, 2026-08-02, workstation dockdemo
?demo=b; full detail in the intake-finding comment):begin_transaction → opened: true execute_dock_operation ×2 → applied: true (both mutations landed) commit_transaction → committed: false, reason: "empty-transaction" list_transactions → {committed: [], redo: []}Root cause (grep receipt):
recordUndo— the capture hook feedingNeo.ai.TransactionService— is invoked only fromsrc/ai/client/InstanceService.mjs(:141, :500, :1143), covering instance create / remove / set-properties.src/ai/client/DockService.mjs(executeDockOperation,capturePerspective,restorePerspective) has no capture hook. The machinery works; the dock mutation paths never record.Additionally (original premise, still true): zero transaction wiring exists anywhere in
apps/agentos/— no demo surface drives begin/commit/undo/redo, so the film cannot show the beat even where recording works.What exists (verified this session):
Neo.ai.TransactionService—src/ai/TransactionService.mjs:95— per-session named transactions: begin / commit / abort, per-session undo & redo stacks (open → committed → undone, redo re-entry, commit clears the redo branch), audit labels vialist_transactionsoverstackOfbegin_transaction,commit_transaction,abort_transaction,undo,redo,list_transactions,save_transaction,replay_transactionsrc/ai/client/InstanceService.mjs(validated reverse-descriptor builders at :500/:1143, hook at :684)The Architectural Reality
undorestoring the prior dock state live — the "it can UNDO that?" momentexecute_dock_operationthe honest reverse is the pre-mutation dockZone.v1 document — the document IS the state; the reverse descriptor must re-apply it through the workspace's document-commit path (onWorkspaceDocumentChange/ refresh projection), not masquerade as a synthetic forward opThe Fix
recordUndocapture hook to the app-worker DockService mutation paths —executeDockOperationfirst (the film's drag-and-dock beat);restorePerspectiveas a state-changing candidate (implementation decides, documented in the PR). Reverse = the pre-mutation document as a data-not-code descriptor, designed againstDockZoneModel's apply path and the InstanceService builder precedent.begin_transaction→ dock mutations →commit_transaction(ops captured) →undo→ assert dock topology restored →redo→ re-applied. Receipt on this ticket.Wire the demo surface minimally— resolved BY DESIGN (2026-08-02): the deterministic film drive is the NL tool chain (the transaction stack is deliberately keyed on the Bridge-stamped(agentId, sessionId)writer pair — seeInstanceService#beginTransaction). App-side transaction cues were rejected: an in-app cue would have to smuggle a pseudo-writer identity into the agent-scoped stack authority. The screenplay narrates via the existing caption machinery while its automation drives NL tools. Recorded in Avoided Traps.test/playwright/e2e/agentos/*NL.spec.mjs) asserting undo restores the pre-transaction dock document and redo re-applies it.Contract Ledger Matrix
src/ai/client/DockService.mjsexecuteDockOperationsrc/ai/TransactionService.mjslifecycle + the baseNeo.ai.client.Service#recordUndocontractDockZoneModel.applyDocument(new dockZone.v1 operation)DockZoneModel.commit()fail-closed normalize+validateoperationsderives from the handler table →get_dock_topology) — this row is the deliberate declaration of that expansion, not a derivation side effectbegin_transaction/commit_transaction/undo/redosrc/ai/TransactionService.mjs:95+ NL tool schemassave_transaction/replay_transactionDemoBWorkspace.mjs:1413Decision Record impact
none— closes a recording gap in existing merged machinery (TransactionService); no ADR-amending surface.Acceptance Criteria
execute_dock_operationinside an open batch records its reverse op:commit_transactionafter dock mutations returnscommitted: truewith the captured ops visible inlist_transactions(receipt: issuecomment-5154289940)#15243→#15245gate)Out of Scope
save_transaction/replay_transactionalready provide#16315, PR #16319)Avoided Traps
moveItem-back) — the document IS the state; the honest reverse re-applies the pre-mutation documenttransaction-wrap/undotour cue) — the stack authority is deliberately keyed on the Bridge-stamped(agentId, sessionId)writer pair; an in-app cue would smuggle a pseudo-writer into it. The screenplay's automation drives the NL tools with its own writer identity; captions narrate through the existing tour machinerytargetSubtreePathis audit metadata, never the enforcement path (TransactionService.mjs:89); nothing at undo time checks the subtree still matches capture-time. The bound is inherent to document-as-state (not a defect), the demo/film is single-writer so it cannot surface there, and it is named here + inbuildDockReverse's JSDoc so no consumer inherits it as an unstated assumptionRelated
#16315(PR #16319)Live latest-open sweep: checked latest 20 open issues at 2026-08-01T23:24Z; no equivalent found. A2A in-flight sweep (60 min): no competing claim on this scope. KB semantic sweep surfaced the TransactionService machinery and NL tool set, no open duplicate.
Origin Session ID: 14f1d6fa-235a-4101-88d4-c9490e3f7fd5
Retrieval Hint: "flagship film undo redo transaction dockdemo witness"