feat(sdk): Expose snapshot.track() and session.addPart() for plugin undo integration #4571

Open
opened 2026-02-16 17:44:38 -05:00 by yindo · 2 comments
Owner

Originally created by @kdcokenny on GitHub (Jan 9, 2026).

Originally assigned to: @thdxr on GitHub.

Problem

The native task tool blocks synchronously, limiting multi-agent orchestration patterns. Community plugins like opencode-background-agents enable async delegation, but changes made by background agents cannot be reverted with /undo because:

  1. PatchParts are only created during LLM stream processing at finish-step
  2. No SDK API exists to create patches programmatically
  3. No plugin hooks for snapshot/patch integration

This forces an impossible choice: either block the parent session (defeating async), or lose undo capability.

Proposed Solution

Expose two primitives that already exist internally, allowing plugins to integrate async operations with the native undo system.

API

// 1. Expose existing Snapshot.track()
// Captures current file state, returns git tree hash
client.snapshot.track(): Promise<string | undefined>

// 2. Expose Session.updatePart() pattern
// Adds a PatchPart to an existing message so /undo can find it
client.session.addPart(
  sessionId: string,
  messageId: string,
  part: { type: 'patch', hash: string, files: string[] }
): Promise<void>

Usage

// Before async delegation starts
const beforeHash = await client.snapshot.track()
if (!beforeHash) throw new Error("Failed to capture snapshot")

// Fire async delegation (non-blocking)
const delegation = await startDelegation(prompt, agent)

// ... parent continues working ...

// When delegation completes (via session.idle listener)
const changedFiles = getFilesChangedByDelegation(delegation)
if (changedFiles.length === 0) return  // Nothing to register

// Register changes with parent's undo history
await client.session.addPart(parentSessionId, parentMessageId, {
  type: 'patch',
  hash: beforeHash,
  files: changedFiles
})

// Now /undo will find this patch and revert the async agent's changes!

Why This Design

  1. Minimal surface area - Just two methods, both exposing existing internals
  2. Matches existing patterns - addPart mirrors Session.updatePart(), validation follows "trust caller, validate structure" philosophy
  3. No architectural changes - Uses existing PatchPart structure and undo scanning
  4. Enables ecosystem innovation - Plugins can build async orchestration with native undo

Prior Art

Tool/Framework Approach
VS Code Extensions push custom undo handlers via workspace.applyEdit()
Cline Shadow Git repo for checkpoints, message-timestamp association
Aider Session-scoped commit tracking, per-file git checkout for undo
Crystal Git worktrees for isolation, commit-based undo

Internal Consistency

This proposal exposes existing internals without modification:

  • Snapshot.track() returns string | undefined → expose as client.snapshot.track()
  • Session.updatePart() pattern → expose as client.session.addPart()
  • PatchPart { type, hash, files } → same structure
  • Undo scans for type === 'patch' → works automatically

References

  • #5887 - Async background sub-agent delegation (primary request)
  • #5840 - Undo system architecture
  • #6792 - Task tool blocks synchronously
  • #7034 - Chat message modification via plugin
  • #7263 - Snapshot management commands

Implementation Notes

The implementation would:

  1. Add track() method to SDK's snapshot namespace (wraps existing Snapshot.track())
  2. Add addPart() method to SDK's session namespace (wraps existing Session.updatePart() pattern)
  3. Use Zod for structure validation (matching existing patterns)
  4. Trust caller for sessionId/messageId (matching existing patterns)

Happy to contribute a PR if this direction is acceptable.

Originally created by @kdcokenny on GitHub (Jan 9, 2026). Originally assigned to: @thdxr on GitHub. ### Problem The native `task` tool blocks synchronously, limiting multi-agent orchestration patterns. Community plugins like [`opencode-background-agents`](https://github.com/kdcokenny/ocx) enable async delegation, but **changes made by background agents cannot be reverted with `/undo`** because: 1. `PatchPart`s are only created during LLM stream processing at `finish-step` 2. No SDK API exists to create patches programmatically 3. No plugin hooks for snapshot/patch integration This forces an impossible choice: either block the parent session (defeating async), or lose undo capability. ### Proposed Solution Expose two primitives that **already exist internally**, allowing plugins to integrate async operations with the native undo system. #### API ```typescript // 1. Expose existing Snapshot.track() // Captures current file state, returns git tree hash client.snapshot.track(): Promise<string | undefined> // 2. Expose Session.updatePart() pattern // Adds a PatchPart to an existing message so /undo can find it client.session.addPart( sessionId: string, messageId: string, part: { type: 'patch', hash: string, files: string[] } ): Promise<void> ``` #### Usage ```typescript // Before async delegation starts const beforeHash = await client.snapshot.track() if (!beforeHash) throw new Error("Failed to capture snapshot") // Fire async delegation (non-blocking) const delegation = await startDelegation(prompt, agent) // ... parent continues working ... // When delegation completes (via session.idle listener) const changedFiles = getFilesChangedByDelegation(delegation) if (changedFiles.length === 0) return // Nothing to register // Register changes with parent's undo history await client.session.addPart(parentSessionId, parentMessageId, { type: 'patch', hash: beforeHash, files: changedFiles }) // Now /undo will find this patch and revert the async agent's changes! ``` ### Why This Design 1. **Minimal surface area** - Just two methods, both exposing existing internals 2. **Matches existing patterns** - `addPart` mirrors `Session.updatePart()`, validation follows "trust caller, validate structure" philosophy 3. **No architectural changes** - Uses existing `PatchPart` structure and undo scanning 4. **Enables ecosystem innovation** - Plugins can build async orchestration with native undo ### Prior Art | Tool/Framework | Approach | |----------------|----------| | **VS Code** | Extensions push custom undo handlers via `workspace.applyEdit()` | | **Cline** | Shadow Git repo for checkpoints, message-timestamp association | | **Aider** | Session-scoped commit tracking, per-file git checkout for undo | | **Crystal** | Git worktrees for isolation, commit-based undo | ### Internal Consistency This proposal exposes existing internals without modification: - `Snapshot.track()` returns `string | undefined` → expose as `client.snapshot.track()` - `Session.updatePart()` pattern → expose as `client.session.addPart()` - `PatchPart { type, hash, files }` → same structure - Undo scans for `type === 'patch'` → works automatically ### References - #5887 - Async background sub-agent delegation (primary request) - #5840 - Undo system architecture - #6792 - Task tool blocks synchronously - #7034 - Chat message modification via plugin - #7263 - Snapshot management commands ### Implementation Notes The implementation would: 1. Add `track()` method to SDK's snapshot namespace (wraps existing `Snapshot.track()`) 2. Add `addPart()` method to SDK's session namespace (wraps existing `Session.updatePart()` pattern) 3. Use Zod for structure validation (matching existing patterns) 4. Trust caller for sessionId/messageId (matching existing patterns) Happy to contribute a PR if this direction is acceptable.
Author
Owner

@github-actions[bot] commented on GitHub (Jan 9, 2026):

This issue is closely related to existing feature requests:

  • #5887: True Async/Background Sub-Agent Delegation (the primary use case motivating this SDK API)
  • #7034: Expose the ability to modify the chat messages thread via a plugin (related plugin API request)

This appears to be a specific SDK API proposal to enable the broader async delegation feature. These issues complement each other rather than duplicate.

@github-actions[bot] commented on GitHub (Jan 9, 2026): This issue is closely related to existing feature requests: - #5887: True Async/Background Sub-Agent Delegation (the primary use case motivating this SDK API) - #7034: Expose the ability to modify the chat messages thread via a plugin (related plugin API request) This appears to be a specific SDK API proposal to enable the broader async delegation feature. These issues complement each other rather than duplicate.
Author
Owner

@mathe00 commented on GitHub (Feb 6, 2026):

Up

This is a workaround for #1286. I would really like to see #1286 or your idea implemented so that we can finally have a more granular checkpoint system.

@mathe00 commented on GitHub (Feb 6, 2026): Up This is a workaround for #1286. I would really like to see #1286 or your idea implemented so that we can finally have a more granular checkpoint system.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#4571