Skip to content

feat(ai-sandbox): SandboxInstanceStore instance durability (BYO) - #988

Merged
tombeckenham merged 4 commits into
mainfrom
feat/persistence-sandbox
Jul 31, 2026
Merged

feat(ai-sandbox): SandboxInstanceStore instance durability (BYO)#988
tombeckenham merged 4 commits into
mainfrom
feat/persistence-sandbox

Conversation

@AlemTuzlak

@AlemTuzlak AlemTuzlak commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Durable sandbox instance resume for multi-process / multi-replica deploy, owned by @tanstack/ai-sandbox.

Based on main (after #984). Locks already live in @tanstack/ai/locks — this PR does not re-ship them. Chat persistence is independent: no stores bag key for sandbox instances.

API

  • SandboxInstanceStore / SandboxInstanceRecord / InMemorySandboxInstanceStore / SandboxInstanceStoreCapability
  • withSandboxInstanceStore(store) — provide before withSandbox
  • defineSandboxInstanceStore — inline BYO typing (same pattern as defineLock / defineMessageStore)
  • withSandbox consumes the capability in ensure (in-memory fallback when absent)
  • Multi-instance: pair with withLocks from @tanstack/ai/locks
  • Conformance: runSandboxInstanceStoreConformance from @tanstack/ai-sandbox/testkit

Docs / e2e

  • docs/sandbox/durability.md (Instance Durability)
  • Locks guide links to durability
  • E2E: sandbox-durability (store shared across independent chat() runs)

Test plan

  • Unit: in-memory store, conformance suite, ensure algorithm, resume-from-store middleware
  • E2E durability route + spec
  • CI green on PR

Summary by CodeRabbit

  • New Features
    • Added durable sandbox instance resume across processes and replicas using shared storage.
    • Added configurable instance storage and distributed locking, with in-memory fallback for single-process use.
    • Added tools for validating custom storage implementations.
  • Breaking Changes
    • Replaced legacy sandbox store integrations with instance store APIs.
  • Documentation
    • Added a sandbox instance durability guide and updated related guidance.
  • Tests
    • Added coverage for durable resume, locking, and storage behavior.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds durable sandbox instance resume through a new store capability, middleware integration, conformance testkit, documentation, package exports, and tests for create-once and resume-on-subsequent-request behavior.

Changes

Sandbox instance durability

Layer / File(s) Summary
Instance store contract and exports
packages/ai-sandbox/src/instance-store.ts, packages/ai-sandbox/src/capabilities.ts, packages/ai-sandbox/src/index.ts, packages/ai-sandbox/src/sandbox.ts
Defines instance records, store operations, in-memory storage, capability wiring, and updated public exports.
Sandbox middleware integration
packages/ai-sandbox/src/middleware.ts, packages/ai-sandbox/tests/ensure.test.ts, packages/ai-sandbox/tests/resume-from-store.test.ts, packages/ai-sandbox/src/workspace.ts
Resolves explicit or ambient instance stores and locks in sandbox request context and verifies resume behavior.
Store conformance and package build
packages/ai-sandbox/src/testkit/*, packages/ai-sandbox/package.json, packages/ai-sandbox/vite.config.ts, packages/ai-sandbox/tests/store.test.ts
Adds store contract tests, publishes the testkit entry point, externalizes Vitest, and tests lock behavior.
Durability end-to-end flow
testing/e2e/src/routes/api.sandbox-durability.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/tests/sandbox-durability.spec.ts, testing/e2e/package.json
Adds a durability API route and verifies stable provider IDs with create-then-resume requests.
Durability documentation and release notes
docs/sandbox/durability.md, docs/sandbox/overview.md, docs/config.json, docs/advanced/locks.md, packages/ai-sandbox/skills/ai-sandbox/SKILL.md, .changeset/sandbox-durability.md
Documents store contracts, locking, middleware ordering, persistence separation, navigation, and the package release note.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: jherr

Sequence Diagram(s)

sequenceDiagram
  participant Playwright
  participant SandboxDurabilityRoute
  participant Chat
  participant SandboxProvider
  participant SandboxInstanceStore
  Playwright->>SandboxDurabilityRoute: POST threadId and runId
  SandboxDurabilityRoute->>Chat: start chat stream
  Chat->>SandboxProvider: create or resume sandbox
  Chat->>SandboxInstanceStore: upsert instance record
  SandboxDurabilityRoute->>SandboxInstanceStore: read stored record
  SandboxDurabilityRoute-->>Playwright: return durability result
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the API, documentation, and tests, but it omits the required Changes, Checklist, and Release Impact sections. Use the repository template headings and include the contribution, test, and release-impact checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the new SandboxInstanceStore feature and its purpose for instance durability.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/persistence-sandbox

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

19 package(s) bumped directly, 32 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-angular 0.3.1 → 1.0.0 Changeset
@tanstack/ai-durable-stream 0.0.0 → 1.0.0 Changeset
@tanstack/ai-memory 0.0.0 → 1.0.0 Changeset
@tanstack/ai-openai 0.17.1 → 1.0.0 Changeset
@tanstack/ai-openrouter 0.15.10 → 1.0.0 Changeset
@tanstack/ai-persistence 0.0.0 → 1.0.0 Changeset
@tanstack/ai-preact 0.11.1 → 1.0.0 Changeset
@tanstack/ai-react 0.18.1 → 1.0.0 Changeset
@tanstack/ai-sandbox 0.2.4 → 1.0.0 Changeset
@tanstack/ai-solid 0.15.1 → 1.0.0 Changeset
@tanstack/ai-svelte 0.15.1 → 1.0.0 Changeset
@tanstack/ai-vue 0.15.1 → 1.0.0 Changeset
@tanstack/openai-base 0.9.9 → 1.0.0 Changeset
@tanstack/ai-acp 0.2.3 → 1.0.0 Dependent
@tanstack/ai-anthropic 0.16.3 → 1.0.0 Dependent
@tanstack/ai-bedrock 0.1.4 → 1.0.0 Dependent
@tanstack/ai-claude-code 0.2.3 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.8 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.11 → 1.0.0 Dependent
@tanstack/ai-codex 0.2.3 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.34 → 1.0.0 Dependent
@tanstack/ai-fal 0.9.12 → 1.0.0 Dependent
@tanstack/ai-gemini 0.20.1 → 1.0.0 Dependent
@tanstack/ai-grok 0.14.9 → 1.0.0 Dependent
@tanstack/ai-grok-build 0.2.3 → 1.0.0 Dependent
@tanstack/ai-groq 0.5.3 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.47 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.47 → 1.0.0 Dependent
@tanstack/ai-mistral 0.2.3 → 1.0.0 Dependent
@tanstack/ai-ollama 0.8.16 → 1.0.0 Dependent
@tanstack/ai-opencode 0.2.3 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.15 → 1.0.0 Dependent
@tanstack/ai-sandbox-cloudflare 0.2.4 → 1.0.0 Dependent
@tanstack/ai-sandbox-daytona 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-docker 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-local-process 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-sprites 0.2.1 → 1.0.0 Dependent
@tanstack/ai-sandbox-vercel 0.2.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.14 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.42.0 → 0.43.0 Changeset
@tanstack/ai-client 0.22.1 → 0.23.0 Changeset
@tanstack/ai-devtools-core 0.4.24 → 0.5.0 Changeset
@tanstack/ai-event-client 0.6.8 → 0.7.0 Changeset
@tanstack/ai-utils 0.3.1 → 0.4.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-mcp 0.2.5 → 0.2.6 Changeset
@tanstack/ai-isolate-cloudflare 0.2.38 → 0.2.39 Dependent
@tanstack/ai-vue-ui 0.2.34 → 0.2.35 Dependent
@tanstack/preact-ai-devtools 0.1.67 → 0.1.68 Dependent
@tanstack/react-ai-devtools 0.2.67 → 0.2.68 Dependent
@tanstack/solid-ai-devtools 0.2.67 → 0.2.68 Dependent
ag-ui 0.0.2 → 0.0.3 Dependent

@nx-cloud

nx-cloud Bot commented Jul 23, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 2d8e35f

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 25s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-31 04:38:55 UTC

@AlemTuzlak
AlemTuzlak force-pushed the feat/persistence-sandbox branch 2 times, most recently from 4b2e68a to e1e51d0 Compare July 23, 2026 12:14
@pkg-pr-new

pkg-pr-new Bot commented Jul 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai@988

@tanstack/ai-acp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-acp@988

@tanstack/ai-angular

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-angular@988

@tanstack/ai-anthropic

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-anthropic@988

@tanstack/ai-bedrock

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-bedrock@988

@tanstack/ai-claude-code

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-claude-code@988

@tanstack/ai-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-client@988

@tanstack/ai-code-mode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode@988

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-skills@988

@tanstack/ai-codex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-codex@988

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-devtools-core@988

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-durable-stream@988

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-elevenlabs@988

@tanstack/ai-event-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-event-client@988

@tanstack/ai-fal

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-fal@988

@tanstack/ai-gemini

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini@988

@tanstack/ai-grok

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok@988

@tanstack/ai-grok-build

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok-build@988

@tanstack/ai-groq

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-groq@988

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-cloudflare@988

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-node@988

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs@988

@tanstack/ai-mcp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mcp@988

@tanstack/ai-memory

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-memory@988

@tanstack/ai-mistral

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mistral@988

@tanstack/ai-ollama

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollama@988

@tanstack/ai-openai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openai@988

@tanstack/ai-opencode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-opencode@988

@tanstack/ai-openrouter

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openrouter@988

@tanstack/ai-persistence

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence@988

@tanstack/ai-preact

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-preact@988

@tanstack/ai-react

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react@988

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@988

@tanstack/ai-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox@988

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-cloudflare@988

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-daytona@988

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-docker@988

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-local-process@988

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-sprites@988

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-vercel@988

@tanstack/ai-solid

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid@988

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@988

@tanstack/ai-svelte

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-svelte@988

@tanstack/ai-utils

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-utils@988

@tanstack/ai-vue

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue@988

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@988

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@988

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/preact-ai-devtools@988

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/react-ai-devtools@988

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/solid-ai-devtools@988

commit: 2d8e35f

@AlemTuzlak
AlemTuzlak force-pushed the feat/persistence-sandbox branch from e1e51d0 to ef3e5f5 Compare July 23, 2026 12:53
@tombeckenham
tombeckenham force-pushed the feat/persistence-core branch from 0b17612 to d2d08e8 Compare July 24, 2026 01:31
@tombeckenham
tombeckenham force-pushed the feat/persistence-sandbox branch from ef3e5f5 to f4c053c Compare July 25, 2026 05:46
@tombeckenham

Copy link
Copy Markdown
Contributor

Rebased onto latest feat/persistence-core (3efe0c1d2) after the bottom of the stack moved.

Adapted to the post-schema-first core design rather than a raw cherry-pick of the pre-refactor commit:

  • Shared LocksCapability + SandboxStoreCapability (and in-memory impls) live in core @tanstack/ai
  • Locks remain on withLocks (not the state bag); optional stores.sandbox is provided by withPersistence when present
  • Backend surface: createDrizzleSandboxStore(db, table) / createPrismaSandboxStore / createD1SandboxStore; Node sqlitePersistence still returns a bag that includes sandbox
  • Sandboxes stay outside the chat BYO schema contract
  • Docs + e2e harness updated for withLocks composition

Local green: typecheck + test:lib for @tanstack/ai-persistence, @tanstack/ai-sandbox, and all four persistence packages (incl. sandbox store conformance).

@tombeckenham
tombeckenham force-pushed the feat/persistence-sandbox branch 2 times, most recently from 4a1adb2 to 860f284 Compare July 26, 2026 22:50
@tombeckenham tombeckenham added the persistence Durable chat state: @tanstack/ai-persistence, client persistence, adapters, locks label Jul 26, 2026
@tombeckenham tombeckenham changed the title feat(persistence): durable sandbox persistence (SandboxStore + shared lock) feat(persistence): sandbox store contract + shared lock tokens (BYO) Jul 26, 2026
tombeckenham added a commit that referenced this pull request Jul 26, 2026
Drop the unlisted @tanstack/ai-persistence import from sandbox unit tests
(use shared capability tokens instead) and make sandbox persistence docs
snippets type-check under kiira.
@tombeckenham
tombeckenham force-pushed the feat/persistence-sandbox branch 3 times, most recently from 0a7be30 to d040b16 Compare July 27, 2026 02:52
Base automatically changed from feat/persistence-core to main July 27, 2026 16:27
@jherr
jherr requested a review from a team as a code owner July 27, 2026 16:27
@AlemTuzlak
AlemTuzlak force-pushed the feat/persistence-sandbox branch from e6716bc to e87d8e1 Compare July 27, 2026 17:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/ai-sandbox/tests/store.test.ts (1)

2-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split and colocate these unit tests with their implementations.

Move the sandbox-store tests beside packages/ai-sandbox/src/instance-store.ts and the lock tests beside packages/ai/src/activities/chat/middleware/locks.ts; this file currently covers sources in two separate locations.

As per coding guidelines, “Place unit tests in *.test.ts files alongside the source they cover.”

Also applies to: 22-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/tests/store.test.ts` around lines 2 - 7, Split the tests
in store.test.ts by implementation: move the InMemorySandboxInstanceStore tests
beside instance-store.ts, and move the InMemoryLockStore tests beside locks.ts
under the chat middleware implementation. Preserve each test’s existing behavior
while ensuring both suites are colocated in *.test.ts files next to their
covered source.

Source: Coding guidelines

testing/e2e/src/routes/api.sandbox-durability.ts (1)

138-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a concurrent-request test for the lock contention path.

This route wires withLocks into the middleware chain, but the only assertions in the accompanying spec exercise strictly sequential requests. A test issuing two concurrent POSTs with the same threadId would validate the actual purpose of the lock (preventing duplicate creation under a race), which is central to this PR's "multi-instance-correct" durability claim.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/routes/api.sandbox-durability.ts` around lines 138 - 171, Add
a concurrent-request test for the /api/sandbox-durability route using the same
threadId, starting both POST requests before awaiting either. Assert that lock
contention prevents duplicate sandbox creation and that the resulting
create/resume counts and provider sandbox identity reflect a single shared
instance, exercising the withLocks middleware path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-sandbox/package.json`:
- Line 62: Update the `@tanstack/ai` dependency entry in package.json from the
workspace:^ protocol to workspace:* while preserving the existing internal
package reference.

In `@packages/ai-sandbox/skills/ai-sandbox/SKILL.md`:
- Around line 222-237: Update the multi-instance example around chat to import
withLocks and LockStore from `@tanstack/ai/locks` instead of the main `@tanstack/ai`
barrel, and replace InMemoryLockStore with the production BYO distributed
LockStore instance. Preserve the existing withSandboxInstanceStore, withLocks,
and withSandbox middleware order.

In `@packages/ai-sandbox/src/instance-store.ts`:
- Around line 80-83: Update InMemoryStore.upsert to normalize omitted optional
fields before storing records, removing undefined-valued properties such as
latestSnapshotId while preserving defined values. Ensure the in-memory fallback
matches the store contract and durable adapters without changing the upsert
return behavior.

In `@packages/ai-sandbox/src/testkit/conformance.ts`:
- Around line 68-77: Update the overwrite fixture in the conformance test around
store.upsert and store.get to seed both latestSnapshotId and latestRunId on the
initial record. After replacing the record, assert that loaded contains neither
optional field, preserving the existing latestSnapshotId assertion and adding
equivalent coverage for latestRunId.

---

Nitpick comments:
In `@packages/ai-sandbox/tests/store.test.ts`:
- Around line 2-7: Split the tests in store.test.ts by implementation: move the
InMemorySandboxInstanceStore tests beside instance-store.ts, and move the
InMemoryLockStore tests beside locks.ts under the chat middleware
implementation. Preserve each test’s existing behavior while ensuring both
suites are colocated in *.test.ts files next to their covered source.

In `@testing/e2e/src/routes/api.sandbox-durability.ts`:
- Around line 138-171: Add a concurrent-request test for the
/api/sandbox-durability route using the same threadId, starting both POST
requests before awaiting either. Assert that lock contention prevents duplicate
sandbox creation and that the resulting create/resume counts and provider
sandbox identity reflect a single shared instance, exercising the withLocks
middleware path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 558feb63-3ea1-478b-a2f2-50ab698d93a2

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab149f and e87d8e1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • .changeset/sandbox-durability.md
  • docs/config.json
  • docs/sandbox/durability.md
  • docs/sandbox/overview.md
  • packages/ai-sandbox/package.json
  • packages/ai-sandbox/skills/ai-sandbox/SKILL.md
  • packages/ai-sandbox/src/capabilities.ts
  • packages/ai-sandbox/src/index.ts
  • packages/ai-sandbox/src/instance-store.ts
  • packages/ai-sandbox/src/middleware.ts
  • packages/ai-sandbox/src/sandbox.ts
  • packages/ai-sandbox/src/store.ts
  • packages/ai-sandbox/src/testkit/conformance.ts
  • packages/ai-sandbox/src/workspace.ts
  • packages/ai-sandbox/tests/ensure.test.ts
  • packages/ai-sandbox/tests/store.test.ts
  • packages/ai-sandbox/vite.config.ts
  • testing/e2e/package.json
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.sandbox-durability.ts
  • testing/e2e/tests/sandbox-durability.spec.ts
💤 Files with no reviewable changes (1)
  • packages/ai-sandbox/src/store.ts

"peerDependencies": {
"@ngrok/ngrok": "^1.0.0",
"@tanstack/ai": "workspace:^"
"@tanstack/ai": "workspace:^",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use workspace:* for the internal peer dependency.

@tanstack/ai uses workspace:^, which does not follow the required internal dependency protocol.

Proposed fix
-    "`@tanstack/ai`": "workspace:^",
+    "`@tanstack/ai`": "workspace:*",

As per coding guidelines, “Use the workspace:* protocol for internal package dependencies in package.json.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@tanstack/ai": "workspace:^",
"`@tanstack/ai`": "workspace:*",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/package.json` at line 62, Update the `@tanstack/ai`
dependency entry in package.json from the workspace:^ protocol to workspace:*
while preserving the existing internal package reference.

Source: Coding guidelines

Comment on lines +222 to +237
import { chat, InMemoryLockStore, withLocks } from '@tanstack/ai'
import {
InMemorySandboxInstanceStore,
withSandbox,
withSandboxInstanceStore,
} from '@tanstack/ai-sandbox'
// Production: your BYO store — docs/sandbox/durability.md
import { instanceStore } from './sandbox-instance-store'

chat({
adapter,
messages,
middleware: [
withSandboxInstanceStore(instanceStore),
withLocks(new InMemoryLockStore()), // multi-instance: distributed lock
withSandbox(sandbox),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tracked files matching SKILL/durability/changeset =="
git ls-files | rg '(^|/)SKILL\.md$|durability|sandbox-instance-store|sandbox-lock-store|sandbox-lock' || true

echo
echo "== target lines in SKILL.md =="
fd -a 'SKILL.md' . | sed 's#^\./##' | while read -r f; do
  if [ "$(basename "$(dirname "$f")")" = "ai-sandbox" ] && grep -q "InMemoryLockStore" "$f"; then
    echo "--- $f"
    wc -l "$f"
    sed -n '200,250p' "$f" | nl -ba -v200
  fi
done

echo
echo "== lock import/usage references =="
rg -n "`@tanstack/ai/locks`|InMemoryLockStore|withLocks|distributed lock|multi-instance" -S . --glob '!node_modules' --glob '!dist' --glob '!build' || true

echo
echo "== candidate store files =="
git ls-files | rg 'sandbox.*(lock|instance).*store\.|lock.*store\.|instance.*store\.' || true

Repository: TanStack/ai

Length of output: 2680


🏁 Script executed:

#!/bin/bash
set -uuo pipefail

echo "== target lines in packages/ai-sandbox/skills/ai-sandbox/SKILL.md =="
sed -n '200,250p' packages/ai-sandbox/skills/ai-sandbox/SKILL.md | awk '{printf "%5d\t%s\n", NR+199, $0}'

echo
echo "== lock/store references in ai-sandbox skill, durability docs, changeset, and repo =="
rg -n "`@tanstack/ai`$|`@tanstack/ai/locks`|InMemoryLockStore|withLocks|distributed lock|multi-instance|sandbox-lock-store|lockStore" packages/ai-sandbox/skills/ai-sandbox/SKILL.md docs/sandbox/durability.md .changeset/sandbox-durability.md -S || true

echo
echo "== package exports / lock entry point =="
for f in packages/ai/package.json packages/ai/src/index.ts packages/ai/src/locks entry points...; do
  if [ -e "$f" ]; then
    echo "--- $f"
    sed -n '1,220p' "$f" | awk '{printf "%5d\t%s\n", NR, $0}'
  fi
done

Repository: TanStack/ai

Length of output: 15067


🏁 Script executed:

#!/bin/bash
set -uuo pipefail

echo "== docs sandbox durability relevant lines =="
sed -n '1,130p' docs/sandbox/durability.md | awk '{printf "%5d\t%s\n", NR, $0}'
echo "---"
sed -n '160,185p' docs/sandbox/durability.md | awk '{printf "%5d\t%s\n", NR+159, $0}'

echo
echo "== changeset relevant lines =="
sed -n '1,80p' .changeset/sandbox-durability.md | awk '{printf "%5d\t%s\n", NR, $0}'

echo
echo "== lock source files =="
git ls-files | rg 'packages/ai/src/(locks|.*lock|locks/)' || true
for f in packages/ai/src/locks.ts packages/ai/src/locks index.ts; do
  if [ -e "$f" ]; then
    echo "--- $f"
    sed -n '1,180p' "$f" | awk '{printf "%5d\t%s\n", NR, $0}'
  fi
done

echo
echo "== lock definitions and InMemoryLockStore references =="
rg -n "class InMemoryLockStore|InMemoryLockStore|LockStore|withLocks|withLocks\\(" packages/ai/src docs packages/ai-sandbox/skills/ai-sandbox/SKILL.md -S || true

Repository: TanStack/ai

Length of output: 14025


Use the locks subpath and a real distributed LockStore in this multi-instance example.

This sandbox durability section introduces @tanstack/ai/locks; importing withLocks / InMemoryLockStore from the main @tanstack/ai barrel is wrong, and InMemoryLockStore is process-local, so the multi-instance: distributed lock use case can still race. Import from @tanstack/ai/locks and pass a BYO distributed LockStore for production.

🧰 Tools
🪛 SkillSpector (2.3.11)

[warning] 200: [PE2] Sudo/Root Execution: Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Remediation: Avoid sudo/root unless strictly required. Prefer least-privilege patterns. If elevation is needed, document the justification and scope.

(Privilege Escalation (PE2))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/skills/ai-sandbox/SKILL.md` around lines 222 - 237,
Update the multi-instance example around chat to import withLocks and LockStore
from `@tanstack/ai/locks` instead of the main `@tanstack/ai` barrel, and replace
InMemoryLockStore with the production BYO distributed LockStore instance.
Preserve the existing withSandboxInstanceStore, withLocks, and withSandbox
middleware order.

Comment on lines +80 to +83
upsert(record: SandboxInstanceRecord): Promise<void> {
this.map.set(record.key, record)
return Promise.resolve()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Normalize omitted optional fields in the in-memory store.

upsert preserves undefined properties. packages/ai-sandbox/src/sandbox.ts writes latestSnapshotId: undefined on the create path (Line 229-237), while the store contract/conformance expects absent optional fields to remain omitted. Normalize optional fields before storing, or construct the create record conditionally, so the in-memory fallback matches durable adapters.

Proposed fix
  upsert(record: SandboxInstanceRecord): Promise<void> {
-   this.map.set(record.key, record)
+   this.map.set(record.key, {
+     key: record.key,
+     provider: record.provider,
+     providerSandboxId: record.providerSandboxId,
+     threadId: record.threadId,
+     updatedAt: record.updatedAt,
+     ...(record.latestSnapshotId !== undefined
+       ? { latestSnapshotId: record.latestSnapshotId }
+       : {}),
+     ...(record.latestRunId !== undefined
+       ? { latestRunId: record.latestRunId }
+       : {}),
+   })
    return Promise.resolve()
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
upsert(record: SandboxInstanceRecord): Promise<void> {
this.map.set(record.key, record)
return Promise.resolve()
}
upsert(record: SandboxInstanceRecord): Promise<void> {
this.map.set(record.key, {
key: record.key,
provider: record.provider,
providerSandboxId: record.providerSandboxId,
threadId: record.threadId,
updatedAt: record.updatedAt,
...(record.latestSnapshotId !== undefined
? { latestSnapshotId: record.latestSnapshotId }
: {}),
...(record.latestRunId !== undefined
? { latestRunId: record.latestRunId }
: {}),
})
return Promise.resolve()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/src/instance-store.ts` around lines 80 - 83, Update
InMemoryStore.upsert to normalize omitted optional fields before storing
records, removing undefined-valued properties such as latestSnapshotId while
preserving defined values. Ensure the in-memory fallback matches the store
contract and durable adapters without changing the upsert return behavior.

Comment on lines +68 to +77
await store.upsert(makeRecord({ latestSnapshotId: 'snap-1' }))
await store.upsert(
makeRecord({ providerSandboxId: 'sb-2', updatedAt: 2 }),
)
const loaded = await store.get('thread-1')
expect(loaded?.providerSandboxId).toBe('sb-2')
expect(loaded?.updatedAt).toBe(2)
// The overwrite dropped latestSnapshotId — a durable store must clear it,
// not retain the prior value.
expect(loaded && 'latestSnapshotId' in loaded).toBe(false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover latestRunId clearing on replacement.

The overwrite fixture only seeds latestSnapshotId, so a store that incorrectly merges and retains latestRunId still passes conformance. Seed both optional fields and assert both are absent after the replacement.

Proposed test update
-      await store.upsert(makeRecord({ latestSnapshotId: 'snap-1' }))
+      await store.upsert(
+        makeRecord({ latestSnapshotId: 'snap-1', latestRunId: 'run-1' }),
+      )
       await store.upsert(
         makeRecord({ providerSandboxId: 'sb-2', updatedAt: 2 }),
       )
@@
       expect(loaded && 'latestSnapshotId' in loaded).toBe(false)
+      expect(loaded && 'latestRunId' in loaded).toBe(false)

Based on supplied contract context, full replacement must clear both optional fields.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await store.upsert(makeRecord({ latestSnapshotId: 'snap-1' }))
await store.upsert(
makeRecord({ providerSandboxId: 'sb-2', updatedAt: 2 }),
)
const loaded = await store.get('thread-1')
expect(loaded?.providerSandboxId).toBe('sb-2')
expect(loaded?.updatedAt).toBe(2)
// The overwrite dropped latestSnapshotId — a durable store must clear it,
// not retain the prior value.
expect(loaded && 'latestSnapshotId' in loaded).toBe(false)
await store.upsert(
makeRecord({ latestSnapshotId: 'snap-1', latestRunId: 'run-1' }),
)
await store.upsert(
makeRecord({ providerSandboxId: 'sb-2', updatedAt: 2 }),
)
const loaded = await store.get('thread-1')
expect(loaded?.providerSandboxId).toBe('sb-2')
expect(loaded?.updatedAt).toBe(2)
// The overwrite dropped latestSnapshotId — a durable store must clear it,
// not retain the prior value.
expect(loaded && 'latestSnapshotId' in loaded).toBe(false)
expect(loaded && 'latestRunId' in loaded).toBe(false)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/src/testkit/conformance.ts` around lines 68 - 77, Update
the overwrite fixture in the conformance test around store.upsert and store.get
to seed both latestSnapshotId and latestRunId on the initial record. After
replacing the record, assert that loaded contains neither optional field,
preserving the existing latestSnapshotId assertion and adding equivalent
coverage for latestRunId.

@tombeckenham tombeckenham changed the title feat(persistence): sandbox store contract + shared lock tokens (BYO) feat(ai-sandbox): SandboxInstanceStore instance durability (BYO) Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-sandbox/tests/resume-from-store.test.ts`:
- Line 1: Move packages/ai-sandbox/tests/resume-from-store.test.ts alongside the
middleware/source module it covers, preserving its tests and imports. Also move
packages/ai-sandbox/tests/store.conformance.test.ts alongside
packages/ai-sandbox/src/testkit/conformance.ts, updating relative imports as
needed; both files should remain *.test.ts unit-test files co-located with their
covered source.
- Around line 81-95: Strengthen the resume-from-store test around run-2 by
storing a provider sandbox ID distinct from the sandbox key after run-1, then
capture the ID supplied to provider.resume and assert it equals that stored
provider ID. Update the existing fake/provider setup and assertions near
run('run-1') and run('run-2') without changing the production flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: de53f0bf-0ca0-4681-a850-e5d320408a57

📥 Commits

Reviewing files that changed from the base of the PR and between e87d8e1 and ed3997b.

📒 Files selected for processing (10)
  • .changeset/sandbox-durability.md
  • docs/advanced/locks.md
  • docs/config.json
  • docs/sandbox/durability.md
  • packages/ai-sandbox/skills/ai-sandbox/SKILL.md
  • packages/ai-sandbox/src/capabilities.ts
  • packages/ai-sandbox/src/index.ts
  • packages/ai-sandbox/src/instance-store.ts
  • packages/ai-sandbox/tests/resume-from-store.test.ts
  • packages/ai-sandbox/tests/store.conformance.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/sandbox/durability.md
  • .changeset/sandbox-durability.md
  • packages/ai-sandbox/src/instance-store.ts
  • packages/ai-sandbox/src/index.ts
  • packages/ai-sandbox/src/capabilities.ts

@@ -0,0 +1,97 @@
import { describe, expect, it } from 'vitest'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Co-locate these unit tests with their covered source modules.

  • packages/ai-sandbox/tests/resume-from-store.test.ts#L1-L1: move alongside its primary middleware/source coverage.
  • packages/ai-sandbox/tests/store.conformance.test.ts#L1-L1: move alongside packages/ai-sandbox/src/testkit/conformance.ts.

As per coding guidelines, “Place unit tests in *.test.ts files alongside the source they cover.”

📍 Affects 2 files
  • packages/ai-sandbox/tests/resume-from-store.test.ts#L1-L1 (this comment)
  • packages/ai-sandbox/tests/store.conformance.test.ts#L1-L1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/tests/resume-from-store.test.ts` at line 1, Move
packages/ai-sandbox/tests/resume-from-store.test.ts alongside the
middleware/source module it covers, preserving its tests and imports. Also move
packages/ai-sandbox/tests/store.conformance.test.ts alongside
packages/ai-sandbox/src/testkit/conformance.ts, updating relative imports as
needed; both files should remain *.test.ts unit-test files co-located with their
covered source.

Sources: Coding guidelines, Learnings

Comment on lines +81 to +95
await run('run-1')
expect(provider.calls.create).toBe(1)
expect(provider.calls.resume).toBe(0)

await run('run-2')
expect(provider.calls.create).toBe(1)
expect(provider.calls.resume).toBe(1)

const key = sandbox.key({
threadId: 'thread-1',
runId: 'run-2',
})
const rec = await instanceStore.get(key)
expect(rec?.latestRunId).toBe('run-2')
expect(rec?.providerSandboxId).toBeTruthy()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the ID passed to provider.resume.

create is given key as its ID, so a regression that resumes using the computed key instead of existing.providerSandboxId still passes these assertions. Store a distinct provider ID after run one and capture/assert resume’s input ID on run two.

Based on provided context, packages/ai-sandbox/src/sandbox.ts passes key to create, while packages/ai-sandbox/tests/fakes.ts resumes the ID it receives.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/tests/resume-from-store.test.ts` around lines 81 - 95,
Strengthen the resume-from-store test around run-2 by storing a provider sandbox
ID distinct from the sandbox key after run-1, then capture the ID supplied to
provider.resume and assert it equals that stored provider ID. Update the
existing fake/provider setup and assertions near run('run-1') and run('run-2')
without changing the production flow.

@tombeckenham

Copy link
Copy Markdown
Contributor

Note: instance-store conformance / testkit

This PR ships runSandboxInstanceStoreConformance under @tanstack/ai-sandbox/testkit (optional Vitest peer), mirroring chat persistence’s runPersistenceConformance pattern: contract + in-memory reference + a suite so BYO stores can prove full-replace upsert, missing-key null, delete no-op, etc. before ensure depends on them.

Open question for reviewers: do we think this is necessary now, and is it even discoverable enough?

  • Necessary? Apps can ship a wrong store and only notice in multi-instance prod. Conformance is the cheap gate. Counter: we don’t ship a packaged sandbox DB backend yet, so maybe overkill until adapters land.
  • Discoverable? Docs + skill mention it; the import is a subpath (@tanstack/ai-sandbox/testkit). Easy to miss if you only look at the root export. Alternatives: drop for v1, or keep but surface harder (e.g. skill “required for production”, example in durability doc only, or delay until feat/persistence-sandbox-adapters).

Preference welcome: keep / drop / keep-but-hide-until-adapters.

AlemTuzlak and others added 2 commits July 31, 2026 14:36
… (BYO)

Additive on top of the core persistence PR (#984, now in main): SandboxInstanceStore contract, InMemorySandboxInstanceStore, withSandboxInstanceStore, and a conformance testkit. withSandbox consumes it in ensure (in-memory fallback); pair withLocks (@tanstack/ai/locks) for multi-instance. Docs, skill, e2e.
- defineSandboxInstanceStore helper (defineLock / defineMessageStore style)
- Skill and comments use @tanstack/ai/locks (not main barrel)
- Conformance suite wired for InMemorySandboxInstanceStore
- Middleware resume test via withSandboxInstanceStore + withLocks + withSandbox
- Locks doc links to sandbox instance durability
autofix-ci Bot and others added 2 commits July 31, 2026 14:36
Replaces the withSandboxInstanceStore pass-through middleware with withSandbox(sandbox, { instances, locks? }). The store had exactly one reader, so routing it through the capability bus bought nothing and cost an ordering rule whose violation silently degraded to the in-memory fallback. SandboxInstanceStoreCapability + provideSandboxInstanceStore stay exported for ambient/platform wiring; precedence is option -> bus -> in-memory.
@tombeckenham
tombeckenham force-pushed the feat/persistence-sandbox branch from 7b28930 to 2d8e35f Compare July 31, 2026 04:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-sandbox/src/index.ts`:
- Around line 18-28: Preserve the legacy `@tanstack/ai-sandbox` exports
InMemorySandboxStore, SandboxStore, and SandboxRecord by adding deprecated
compatibility re-exports that point to their replacements in `@tanstack/ai`, and
document the migration path. Keep the current SandboxInstanceStore exports
intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d2b14974-f6b6-47b1-aa1c-c6c5ab2005e1

📥 Commits

Reviewing files that changed from the base of the PR and between 7b28930 and 2d8e35f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • .changeset/sandbox-durability.md
  • docs/advanced/locks.md
  • docs/config.json
  • docs/sandbox/durability.md
  • docs/sandbox/overview.md
  • packages/ai-sandbox/package.json
  • packages/ai-sandbox/skills/ai-sandbox/SKILL.md
  • packages/ai-sandbox/src/capabilities.ts
  • packages/ai-sandbox/src/index.ts
  • packages/ai-sandbox/src/instance-store.ts
  • packages/ai-sandbox/src/middleware.ts
  • packages/ai-sandbox/src/sandbox.ts
  • packages/ai-sandbox/src/store.ts
  • packages/ai-sandbox/src/testkit/conformance.ts
  • packages/ai-sandbox/src/workspace.ts
  • packages/ai-sandbox/tests/ensure.test.ts
  • packages/ai-sandbox/tests/resume-from-store.test.ts
  • packages/ai-sandbox/tests/store.conformance.test.ts
  • packages/ai-sandbox/tests/store.test.ts
  • packages/ai-sandbox/vite.config.ts
  • testing/e2e/package.json
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.sandbox-durability.ts
  • testing/e2e/tests/sandbox-durability.spec.ts
💤 Files with no reviewable changes (1)
  • packages/ai-sandbox/src/store.ts
🚧 Files skipped from review as they are similar to previous changes (20)
  • docs/advanced/locks.md
  • packages/ai-sandbox/vite.config.ts
  • packages/ai-sandbox/src/workspace.ts
  • packages/ai-sandbox/package.json
  • docs/config.json
  • testing/e2e/tests/sandbox-durability.spec.ts
  • packages/ai-sandbox/tests/store.conformance.test.ts
  • .changeset/sandbox-durability.md
  • testing/e2e/package.json
  • packages/ai-sandbox/tests/store.test.ts
  • docs/sandbox/overview.md
  • testing/e2e/src/routes/api.sandbox-durability.ts
  • docs/sandbox/durability.md
  • packages/ai-sandbox/src/capabilities.ts
  • packages/ai-sandbox/src/sandbox.ts
  • testing/e2e/src/routeTree.gen.ts
  • packages/ai-sandbox/src/middleware.ts
  • packages/ai-sandbox/tests/ensure.test.ts
  • packages/ai-sandbox/src/instance-store.ts
  • packages/ai-sandbox/tests/resume-from-store.test.ts

Comment on lines +18 to +28
export {
SandboxInstanceStoreCapability,
getSandboxInstanceStore,
provideSandboxInstanceStore,
InMemorySandboxInstanceStore,
defineSandboxInstanceStore,
} from './instance-store'
export type {
SandboxInstanceStore,
SandboxInstanceRecord,
} from './instance-store'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n \
  '\b(InMemorySandboxStore|SandboxStore|SandboxRecord)\b|`@tanstack/ai-sandbox/`(store|instance-store)' \
  packages testing docs

rg -n -C 3 '"exports"|"\./store"|"\./instance-store"' \
  packages/ai-sandbox/package.json

Repository: TanStack/ai

Length of output: 4587


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '--- package export map ---\n'
cat -n packages/ai-sandbox/package.json | sed -n '1,80p'

printf '\n--- repository exports for sandbox root/import paths ---\n'
rg -n '(`@tanstack/ai-sandbox`\b|from ["'\''](\/|\.\/)dist|from ["'\'']\./source\/|from ["'\'']\./src\/|from ["'\'']\./dist|from ["'\'']\./source|from ["'\'']\./src)' packages --glob '*.{js,ts,tsx,md,mdx,json}'

printf '\n--- package files and relevant source ---\n'
git ls-files packages/ai-sandbox
printf '\n--- instance-store and store files ---\n'
git ls-files packages/ai-sandbox/src | sed -n '1,200p'

Repository: TanStack/ai

Length of output: 240


Preserve or version the removed @tanstack/ai-sandbox exports.

If InMemorySandboxStore, SandboxStore, or SandboxRecord are published to @tanstack/ai-sandbox, dropping these exports is a compile-time breaking change. Add deprecated re-exports, document the migration to @tanstack/ai, or release this as a deliberate breaking change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/src/index.ts` around lines 18 - 28, Preserve the legacy
`@tanstack/ai-sandbox` exports InMemorySandboxStore, SandboxStore, and
SandboxRecord by adding deprecated compatibility re-exports that point to their
replacements in `@tanstack/ai`, and document the migration path. Keep the current
SandboxInstanceStore exports intact.

@tombeckenham
tombeckenham self-requested a review July 31, 2026 04:48
@tombeckenham
tombeckenham merged commit 1cb04d5 into main Jul 31, 2026
10 checks passed
@tombeckenham
tombeckenham deleted the feat/persistence-sandbox branch July 31, 2026 04:50
AlemTuzlak added a commit that referenced this pull request Jul 31, 2026
main squash-merged the two PRs this branch was stacked on (#988 sandbox
instance durability, #1011 generation run persistence), so this branch's own
copy of that foundation collided with the squashes. 17 files conflicted.

Notable resolutions, beyond taking the union:

- #1004 made `RunStore.findActiveRun` REQUIRED, and main added an explicit
  store-contract evolution policy naming that exact regression. This branch had
  relocated `RunStore` into `@tanstack/ai` with `findActiveRun?` optional, and
  `run-store.ts` merged CLEANLY -- so keeping our side would have silently
  reverted #1004. `findActiveRun` is now required in core too, dropped from the
  conformance suite's `skipMethods` union, and `fenceRunStore` forwards it
  unconditionally. `listByThread`/`listReclaimable` stay optional.
- Generation persistence moved to main's `generationRuns` store, but main writes
  `status: 'interrupted'` with a `finishedAt` on abort. This branch made
  `interrupted` non-terminal ("parked, waiting for a human"), so that pairing
  would leave an aborted generation looking permanently active. Now writes
  `'aborted'`.
- `snapshotStatus` in `reconstruct-generation.ts` switched exhaustively over the
  old 4-member `RunStatus`; ours adds `aborted`, so an aborted generation fell
  through and the function returned `undefined`. Now maps to `'error'`.
- `chat-persistence.md`: kept main's new lifecycle mermaid diagram, corrected to
  the current semantics (completed/failed/aborted terminal, interrupted parked,
  detached stays running).
- `docs/sandbox/durability.md`: kept our real `import` over main's
  `declare const`, per the repo's kiira snippet rule.

Verified: 17 typechecks green (including examples/ts-react-chat and
testing/e2e), oxlint green, kiira 911/911, test:docs, sherif, knip and oxfmt all
clean. Unit: ai 1409, ai-persistence 150, ai-client 585, ai-react 176,
ai-durable-stream 45, ai-sandbox 602/603 (pre-existing Windows path case).

E2E not run: port 4010 is held by an unrelated showcase-aimock container and
another worktree's in-flight Playwright run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

persistence Durable chat state: @tanstack/ai-persistence, client persistence, adapters, locks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants