Skip to content

feat(ai-harness): session lifecycle with handles, observe, retention, host plugins, and a purging delete - #1813

Open
AlemTuzlak wants to merge 14 commits into
feat/harness-accessfrom
feat/harness-lifecycle
Open

AlemTuzlak wants to merge 14 commits into
feat/harness-accessfrom
feat/harness-lifecycle

Conversation

@AlemTuzlak

Copy link
Copy Markdown
Contributor

Every session that opens also closes, and memory per session has a set limit. host.open returns a handle that you release, and the session closes when no handle holds it and it is idle. Read routes use a new read-only host.observe, so a read never starts plugins or writes. This is PR 4 of the harness redesign stack for #1755. It targets #1812 (feat/harness-access).

🎯 Changes

  • Handles.
    • await session.close() releases only your handle, and await using session = await host.open(...) does it for you.
    • The shared session closes after lifecycle.idleCloseMs (default 60 000) with no handle. A handle that its holder dropped is released by garbage collection, with one warning.
    • lifecycle.maxOpen (default 500) closes the least recently used idle sessions first.
    • A call on a handle whose session closed opens it again. host.close() refuses later opens.
    • Every caller releases what it opens: the HTTP routes, the socket, harnessText, the stdio worker, ACP session/close, and MCP calls.
  • host.observe(harness, { threadId, principal }).
    • It returns an ObservedSession with events, transcript, snapshot, agentTranscript, media reads, usage, entry, and logState.
    • It never mounts plugins, writes, claims, or recovers. Observers of one log share one fold.
    • Every HTTP read route and the MCP status and media reads use it. describe still opens the session.
  • Retention. lifecycle.retention: { settledOperations, settledAgentRuns, eventBytes } bounds the inputs, operations, agent runs, the feed, and the log tail. One record per input is trimmed in one step. This fixes B5: a settled() no longer hangs when agent runs push out turn entries.
  • Host-lifetime plugins. definePlugin({ lifetime: 'host', setup }) is set up once per host and harness, and shared by a reference count. Its session part adds what each session needs. mcp() runs stdio servers once per host this way, and OAuth servers stay per user.
  • host.sessions.delete purges the open sessions, the logs or messages, the index rows, uploads and generated artifacts, the harness metadata, and every child thread, depth first.
  • Cancellation.
    • OAuth flows take a signal, and the loopback deadline is unref'd. A cancel or a session close stops a running /connect at once.
    • OAuthConfig.issuer checks the iss of a callback.
    • In every CLI mode, SIGINT and SIGTERM cancel the turn first, then stop with 130 or 143. The new runCli({ signals }) option takes the signal source.

Docs: a new docs/harness/lifecycle.md, and changes in sessions, plugins, mcp, cli, auth, durable-sessions, connect, custom-ui, and overview. Changesets are edited in place.

Decisions made while building:

  1. One name. delete purges, so there is no second purge name. Plugin state, runs, interrupts, the inbox, and work claims stay for now: PR 5 moves them into the log or removes them.
  2. Released handles do not keep sessions open. A FinalizationRegistry releases dropped handles.
  3. A read of a missing thread with access is 404. The client's events() already retries a 404, so a new chat works.
  4. refreshCredential takes an options object ({ fetch, signal }) as its third argument, to match exchangeCode.
  5. definePlugin infers its definition only from the argument (NoInfer). A plugins array around the call no longer breaks the inference of an inline plugin.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Testing

Commands run:

  1. Builds and test:types of ai-harness, ai-mcp, ai-acp, and ai-harness-cli, plus test:types of the other definePlugin users (ai-skills, ai-code-mode, ai-sandbox). All pass.
  2. Each node ran its new tests and the test files that it changed, with --maxWorkers=2: handles, observe, retention, host-plugins, purge, signals, the OAuth tests, and the touched MCP, ACP, and CLI tests. All pass.
  3. oxfmt --check on every changed file, pnpm test:docs, and kiira check on the changed docs pages.

Not run locally: the full suites, test:oxlint, knip, E2E (including the new harness-lifecycle.spec.ts), and coverage. CI runs them.

Manual test:

  1. Run pnpm --dir packages/ai-harness exec vitest run tests/handles.test.ts tests/observe.test.ts tests/purge.test.ts.
  2. Read the leak test in tests/handles.test.ts: 10 000 sessions open and close through the handler, and the host ends with 0 open sessions.
  3. Read tests/observe.test.ts: a read route on a thread with a plugin that counts its setups mounts nothing.

How this PR makes testing easy: the new test files pin each part: handles, observe, retention, host-plugins, purge, and the CLI signals. testing/e2e/tests/harness-lifecycle.spec.ts covers a delete that removes media and messages, and reads after an idle close.

Risk / rollback

Medium. session.close() now releases only a handle, and every read route moved to observe. The harness is not released yet, and all callers in this repo are moved. To undo, revert this PR.

Public API change

Before

const session = await host.open(harness, { threadId, principal })
await session.close() // closed the session for every user
await host.sessions.delete({ threadId, principal }) // the entry and the transcript

After

await using session = await host.open(harness, { threadId, principal }) // released at the end of the block
const view = await host.observe(harness, { threadId, principal }) // read only
createHarnessHost({ lifecycle: { idleCloseMs: 60_000, maxOpen: 500, retention: { eventBytes: 8_000_000 } } })
definePlugin({ name: 'acme/index', lifetime: 'host', setup: ({ resources }) => ({ tools }) })
await host.sessions.delete({ threadId, principal }) // purges the thread and its children

@changeset-bot

changeset-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2123a0f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 8 packages
Name Type
@tanstack/ai-harness-cli Minor
@tanstack/ai-harness Minor
@tanstack/ai-mcp Minor
@tanstack/ai-acp Minor
@tanstack/ai-code-mode Patch
@tanstack/ai-sandbox Patch
@tanstack/ai-skills Patch
@tanstack/ai-grok-build Patch

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: TanStack/ai/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d57ecf70-f8d2-4fc7-99e5-5cb9514e9f7c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@nx-cloud

nx-cloud Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 2123a0f

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

☁️ Nx Cloud last updated this comment at 2026-10-17 17:57:32 UTC

@pkg-pr-new

pkg-pr-new Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-acp

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

@tanstack/ai-angular

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

@tanstack/ai-anthropic

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

@tanstack/ai-bedrock

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

@tanstack/ai-byteplus

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-byteplus@1813

@tanstack/ai-claude-code

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

@tanstack/ai-client

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

@tanstack/ai-cloudflare

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-snippets

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

@tanstack/ai-codex

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

@tanstack/ai-cohere

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-cohere@1813

@tanstack/ai-compaction

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-compaction@1813

@tanstack/ai-devtools-core

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

@tanstack/ai-durable-stream

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-grok-build

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

@tanstack/ai-groq

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

@tanstack/ai-harness

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-harness@1813

@tanstack/ai-harness-cli

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-harness-cli@1813

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-daytona@1813

@tanstack/ai-isolate-e2b

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-e2b@1813

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-isolate-quickjs-bun

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

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-llmgateway@1813

@tanstack/ai-lovable

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-lovable@1813

@tanstack/ai-mcp

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

@tanstack/ai-memory

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

@tanstack/ai-mistral

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

@tanstack/ai-octane

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-octane@1813

@tanstack/ai-ollama

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

@tanstack/ai-ollaya

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollaya@1813

@tanstack/ai-openai

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

@tanstack/ai-opencode

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

@tanstack/ai-openrouter

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

@tanstack/ai-perplexity

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-perplexity@1813

@tanstack/ai-persistence

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-reactor

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-reactor@1813

@tanstack/ai-remix

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-remix@1813

@tanstack/ai-sandbox

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

@tanstack/ai-sandbox-blaxel

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

@tanstack/ai-sandbox-boxd

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

@tanstack/ai-sandbox-cloudflare

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

@tanstack/ai-sandbox-daytona

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

@tanstack/ai-sandbox-docker

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

@tanstack/ai-sandbox-e2b

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

@tanstack/ai-sandbox-local-process

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

@tanstack/ai-sandbox-railway

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

@tanstack/ai-sandbox-sprites

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

@tanstack/ai-sandbox-upstash-box

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-upstash-box@1813

@tanstack/ai-sandbox-vercel

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

@tanstack/ai-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-skills@1813

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-typesafe

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-typesafe@1813

@tanstack/ai-utils

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

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vercel-gateway@1813

@tanstack/ai-vertex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vertex@1813

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/ai-worldlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-worldlabs@1813

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

@tanstack/svelte-ai-devtools

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

commit: 2123a0f

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Coverage

✅ Coverage held across 30 compared package(s).

Each package is measured twice in this job — on this PR and on its merge-base with main — and compared. A metric fails only when it is at or above 60% on the merge-base and under 60% on this PR. Other drops are listed but do not fail. Packages your PR doesn't affect are not measured and not listed.

package statements branches functions lines
ai-acp 82.31% (-0.19) 71.64% (-0.35) 77.00% (-0.20) 84.13% (-0.18) ok
ai-claude-code 88.93% (0.00) 76.88% (0.00) 86.88% (0.00) 90.40% (0.00) ok
ai-code-mode 92.71% (0.00) 85.66% (0.00) 89.06% (0.00) 93.51% (0.00) ok
ai-code-mode-snippets 87.13% (0.00) 75.67% (0.00) 91.11% (0.00) 88.39% (0.00) ok
ai-codex 94.55% (0.00) 85.10% (0.00) 89.28% (0.00) 95.56% (0.00) ok
ai-durable-stream 86.70% (0.00) 82.25% (0.00) 92.30% (0.00) 88.72% (0.00) ok
ai-grok-build 79.19% (0.00) 63.56% (0.00) 77.39% (0.00) 81.02% (0.00) ok
ai-harness 94.28% (-0.30) 88.45% (-0.41) 93.85% (-0.61) 95.80% (-0.15) ok
ai-harness-cli 91.12% (+5.18) 84.77% (+3.48) 84.96% (+4.10) 92.51% (+5.77) ok
ai-isolate-cloudflare 94.11% (0.00) 70.66% (0.00) 100.00% (0.00) 93.96% (0.00) ok
ai-isolate-daytona 90.25% (0.00) 80.59% (-0.75) 100.00% (0.00) 90.15% (0.00) ok
ai-isolate-e2b 92.11% (0.00) 81.48% (0.00) 97.36% (0.00) 95.05% (0.00) ok
ai-isolate-node 84.00% (0.00) 43.39% (0.00) 100.00% (0.00) 83.78% (0.00) ok
ai-isolate-quickjs 89.07% (0.00) 76.41% (0.00) 90.90% (0.00) 90.30% (0.00) ok
ai-isolate-quickjs-bun 7.76% (0.00) 8.08% (0.00) 5.00% (0.00) 8.33% (0.00) ok
ai-mcp 96.46% (-0.25) 91.51% (-0.22) 96.80% (-0.15) 98.38% (-0.21) ok
ai-opencode 69.04% (0.00) 60.82% (0.00) 65.26% (0.00) 70.87% (0.00) ok
ai-sandbox 77.96% (-0.06) 83.06% (-0.04) 75.22% (-0.11) 78.52% (-0.05) ok
ai-sandbox-blaxel 87.64% (0.00) 77.18% (0.00) 82.26% (0.00) 89.71% (0.00) ok
ai-sandbox-boxd 95.86% (0.00) 87.12% (0.00) 95.31% (0.00) 98.17% (0.00) ok
ai-sandbox-cloudflare 67.91% (0.00) 57.47% (0.00) 71.79% (0.00) 69.62% (0.00) ok
ai-sandbox-daytona 82.70% (0.00) 81.89% (0.00) 67.14% (0.00) 85.18% (0.00) ok
ai-sandbox-docker 80.15% (0.00) 71.49% (0.00) 77.38% (0.00) 82.13% (0.00) ok
ai-sandbox-e2b 94.42% (0.00) 88.97% (0.00) 93.24% (0.00) 98.63% (0.00) ok
ai-sandbox-local-process 68.00% (0.00) 53.59% (0.00) 75.86% (0.00) 68.88% (0.00) ok
ai-sandbox-railway 94.75% (0.00) 92.68% (0.00) 90.90% (0.00) 97.59% (0.00) ok
ai-sandbox-sprites 73.23% (0.00) 59.70% (0.00) 73.83% (0.00) 76.17% (0.00) ok
ai-sandbox-upstash-box 94.82% (0.00) 91.52% (0.00) 91.04% (0.00) 97.76% (0.00) ok
ai-sandbox-vercel 44.67% (0.00) 38.18% (0.00) 35.18% (0.00) 45.76% (0.00) ok
ai-skills 83.59% (0.00) 68.58% (0.00) 87.36% (0.00) 85.63% (0.00) ok

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: maintainer The ball is in the maintainers’ court

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants