Summary
Two new Neural Link write tools — begin_transaction + commit_transaction — that group multiple agent mutations into a single undoable named transaction. The last named-batching piece of the #9848 undo-stack roadmap (after core #13230, undo #13221, redo #13304, list_transactions #13326).
Today every mutation auto-wraps into its own single-op transaction (InstanceService.recordUndo: begin→record→commit per op), so "undo" reverts exactly one mutation. A conversational-UI intent like "add a summary grid" may be 5 mutations; the user expects one "undo" to revert the whole intent, not five. Named batching delivers that — and #9848 explicitly scopes "named transactions" + "atomic batching".
Design
begin_transaction({name}) — opens a named tx for the (agentId, sessionId) writer via TransactionService.begin({id, txId}) (the core already supports an arbitrary txId). Returns the txId. Fail-closed: no writer identity → error.
commit_transaction() — commits the writer's currently-open named tx (TransactionService.commit), clearing the redo branch (divergence). Fail-closed: no open tx → error.
recordUndo routing (the load-bearing change): at the start of recordUndo, inspect the open-batch probe (openTxId):
- open named batch present (the agent called
begin_transaction) → record the op into it; no per-op commit (the batch accumulates).
- no open batch → auto-wrap as today (
begin→record→commit single-op). The open-batch case MUST short-circuit first — the auto-wrap's begin would abort the open batch.
- A batched (multi-op) tx undoes/redoes as a unit —
undo/redo already iterate a tx's ops. (Once list_transactions #13329 lands, it also reports a batch with opCount > 1 + all labels — that cross-tool assertion is follow-up #13335.)
Acceptance Criteria
begin_transaction({name?}) write tool: opens a named tx for the writer; returns {txId}; fail-closed on no-writer-identity. 6-site wiring + NeuralLink.md doc-row + OpenApiValidatorCompliance write-tier fixture.
commit_transaction() write tool: commits the writer's open named tx; clears redo; fail-closed on no-open-tx. 6-site wiring + doc-row + write-tier fixture.
recordUndo routes into an open named batch when present; preserves the per-mutation auto-wrap when none is open (the merged single-op capture unchanged — regression-covered).
- A multi-op batched tx undoes/redoes as a unit. *(The
list_transactions-reports-a-batch clause — opCount + all labels — is a #13329 × #13331 cross-tool assertion that can't be verified on either branch alone; split to follow-up #13335, out of this ticket's Resolves-scope, per the #13333 review.)*
- Unit (routing both paths + the 2 tools' open/commit/fail-closed + undo-as-unit + redo-as-unit) + compliance (tiers, doc==openapi parity via the #13318 guard) green.
Out of scope (follow-ups)
list_transactions reports a batch (opCount + labels) — #13335 (needs #13329 + #13331 both merged).
abort_transaction (discard an open batch without committing) — the TransactionService.abort core exists; a thin tool can follow if the batch-abort UX is needed.
- Nested / auto-named batches — single open named batch per writer (mirrors the single-open-tx core model).
Residual risks
- The
recordUndo routing must not break the merged per-mutation auto-wrap (AC3 regression coverage is the guard).
- A mutation denied by the WriteGuard mid-batch isn't recorded (the batch stays open, missing that op); the agent sees the deny and can
commit_transaction the partial batch. Acceptable for Slice-2 — it matches per-op deny semantics today.
Context
Parent: #9848 (undo-stack umbrella). Refs #13012 (Agent Harness Pillar-2). Core API: src/ai/TransactionService.mjs (begin/record/commit/abort/stackOf/openTxId). Capture hook: src/ai/client/InstanceService.mjs recordUndo.
Authored by Claude Opus 4.8 (Claude Code), @neo-opus-vega (Vega).
Summary
Two new Neural Link write tools —
begin_transaction+commit_transaction— that group multiple agent mutations into a single undoable named transaction. The last named-batching piece of the #9848 undo-stack roadmap (after core #13230, undo #13221, redo #13304, list_transactions #13326).Today every mutation auto-wraps into its own single-op transaction (
InstanceService.recordUndo:begin→record→commitper op), so "undo" reverts exactly one mutation. A conversational-UI intent like "add a summary grid" may be 5 mutations; the user expects one "undo" to revert the whole intent, not five. Named batching delivers that — and #9848 explicitly scopes "named transactions" + "atomic batching".Design
begin_transaction({name})— opens a named tx for the(agentId, sessionId)writer viaTransactionService.begin({id, txId})(the core already supports an arbitrarytxId). Returns thetxId. Fail-closed: no writer identity → error.commit_transaction()— commits the writer's currently-open named tx (TransactionService.commit), clearing the redo branch (divergence). Fail-closed: no open tx → error.recordUndorouting (the load-bearing change): at the start ofrecordUndo, inspect the open-batch probe (openTxId):begin_transaction) →recordthe op into it; no per-op commit (the batch accumulates).begin→record→commitsingle-op). The open-batch case MUST short-circuit first — the auto-wrap'sbeginwould abort the open batch.undo/redoalready iterate a tx's ops. (Oncelist_transactions#13329 lands, it also reports a batch withopCount > 1+ all labels — that cross-tool assertion is follow-up #13335.)Acceptance Criteria
begin_transaction({name?})write tool: opens a named tx for the writer; returns{txId}; fail-closed on no-writer-identity. 6-site wiring +NeuralLink.mddoc-row +OpenApiValidatorCompliancewrite-tier fixture.commit_transaction()write tool: commits the writer's open named tx; clears redo; fail-closed on no-open-tx. 6-site wiring + doc-row + write-tier fixture.recordUndoroutes into an open named batch when present; preserves the per-mutation auto-wrap when none is open (the merged single-op capture unchanged — regression-covered).list_transactions-reports-a-batch clause —opCount+ alllabels— is a #13329 × #13331 cross-tool assertion that can't be verified on either branch alone; split to follow-up #13335, out of this ticket's Resolves-scope, per the #13333 review.)*Out of scope (follow-ups)
list_transactionsreports a batch (opCount + labels) — #13335 (needs #13329 + #13331 both merged).abort_transaction(discard an open batch without committing) — theTransactionService.abortcore exists; a thin tool can follow if the batch-abort UX is needed.Residual risks
recordUndorouting must not break the merged per-mutation auto-wrap (AC3 regression coverage is the guard).commit_transactionthe partial batch. Acceptable for Slice-2 — it matches per-op deny semantics today.Context
Parent: #9848 (undo-stack umbrella). Refs #13012 (Agent Harness Pillar-2). Core API:
src/ai/TransactionService.mjs(begin/record/commit/abort/stackOf/openTxId). Capture hook:src/ai/client/InstanceService.mjsrecordUndo.Authored by Claude Opus 4.8 (Claude Code), @neo-opus-vega (Vega).