Skip to content

feat(ai-harness): the agent tree, with typed agent actions, limits and budgets that hold, run inheritance, shared state, durable groups, and harnessAgent as a child run - #1818

Open
AlemTuzlak wants to merge 13 commits into
feat/harness-waitsfrom
feat/harness-agent-tree

Conversation

@AlemTuzlak

Copy link
Copy Markdown
Contributor

Agents that code starts are now typed runs in one run tree in the log. Limits and budgets hold across the whole tree and across restarts, children follow their parent, and a group of agents can run with a concurrency limit and pick up its children after a crash. harnessAgent runs another harness as a child run of the same session. This is PR 8 of the harness redesign stack for #1755. It targets #1817 (feat/harness-waits).

🎯 Changes

  • Typed agent actions.
    • ctx.harness.agents.start(worker, input, options?), run(worker, input, options?), and group({ concurrency?, onFailure?, key? }, (g) => [g.start(...)]). The input type comes from the worker's input schema, and the result type comes from its output.
    • Plugin ctx.agents has the same type. g.run and g.runSettled are gone: use g.start, and onFailure: 'collect' gives AgentSettled entries.
    • Start options are grouped: key, limits: { timeoutMs, maxAttempts }, budget: { maxTokens, maxCost }, onModelError.
  • The run tree. RunNode (turn, agent, tool, command) lives in the log (harness.run.node). agentRuns() reads it instead of scanning every input (B4).
  • Limits and budgets.
    • maxDepth and maxCalls count per tree. maxConcurrent counts per session for top-level starts, and a start over the limit waits as queued. The counts come from the log, so they survive a restart.
    • Every usage record has runId, and a run keeps its usage sum. A run over its budget fails with code budget. A timed-out run fails with code timeout.
    • A failed start gives its budget call back (a bug before).
  • Run inheritance (D5).
    • A background run that a turn starts keeps the turn's plugins until it settles.
    • A child's signal follows its parent: cancel, timeout, and budget pass down, and a shutdown stays a shutdown. Cancelling a turn now also cancels the background runs it started.
    • Tool-mode and routed children get a real ctx.harness and share the turn's tree budget.
  • Shared state. ctx.harness.state({ key, initial }) gives a { get, update } handle. It is kept in the log, shared by key in one session, and copied by a fork.
  • Durable groups. A concurrency gate per group. With resume: true, children get keyed input ids, so a run that runs again after a crash binds its old children instead of starting new ones.
  • Routed agents take steers, late joins, beforeFinish, and onModelError, and each answer stays in the transcript.
  • harnessAgent as a child run.
    • Inside a session, it opens a child session of the other harness on <root>/agent/<run id>, in the root log.
    • It runs for the parent's user and keys, its usage counts against the parent run's budget, and its waits show at the root.
    • All its events reach the parent with parentRunId. host.open refuses a child thread id.
    • Outside a session it keeps its in-memory host.

Docs: subagents.md (agents from code, groups, limits, budgets, the run tree, shared state, routed agents, harnessAgent), plugins.md, session-log.md, usage.md, shared-logs.md, and others. The changesets are edited in place.

Decisions made while building:

  1. Keyed child ids only with resume: true. With keyed ids in every group, an onModelError retry would rebind the child that failed, so the retry could never succeed.
  2. A run that waits for a person keeps its maxConcurrent slot, the same as core does for children. A queued start does not survive a restart: the next host refuses it with expired_on_restart.
  3. onModelError works live only. A run that another host takes over logs one warning.
  4. A harnessAgent child's waits are live only. The root lists and routes them while the child session is open on this host. Another host and host.observe do not see them.

✅ 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 of the harness and its dependencies. test:types of ai, ai-harness, ai-mcp, ai-acp, ai-harness-cli, ai-code-mode, ai-skills, and ai-sandbox. All pass.
  2. vitest related on every changed ai-harness source file, with --maxWorkers=2: 127 files, 1,785 tests, all pass after merging the latest PR 7.
  3. test:oxlint of ai-harness, pnpm test:docs, kiira check on the changed pages, and oxfmt --check on every changed file.

Not run locally: knip, E2E (including the new harness-agent-tree.spec.ts), and coverage. CI runs them.

Manual test:

  1. Run pnpm --dir packages/ai-harness exec vitest run tests/agent-group.test.ts tests/agent-limits.test.ts tests/harness-agent.test.ts.
  2. Read the host-stop test in tests/agent-group.test.ts: a group stops with its host, and the run on the next host binds its old children.
  3. Read tests/agent-limits.test.ts: a run over its token budget fails with code budget, and its parent sees the error.

How this PR makes testing easy: the new test files pin each part: run-tree, agent-limits, run-inheritance, shared-state, agent-group, and harness-agent. testing/e2e/tests/harness-agent-tree.spec.ts covers a group with a concurrency gate, a run budget, shared state, and a harnessAgent child.

Risk / rollback

High. This changes how agent runs start, count, inherit, and stop. Cancelling a turn now also cancels its background runs. The harness is not released yet, and all callers in this repo are moved. To undo, revert this PR.

Public API change

Before

const [a, b] = await ctx.agents.group(async (g) => [await g.run(research, 'x'), await g.run(write, 'y')])
await ctx.agents.start(worker, input, { timeoutMs: 60_000 })

After

const [a, b] = await ctx.harness.agents.group({ concurrency: 2 }, (g) => [g.start(research, { topic: 'x' }), g.start(write, { draft: 'y' })])
await ctx.harness.agents.start(worker, input, { limits: { timeoutMs: 60_000 }, budget: { maxTokens: 20_000 } })
const count = ctx.harness.state({ key: 'count', initial: 0 })

…roup, with grouped options, for plugins and agent code
# Conflicts:
#	packages/ai-harness/src/agents.ts
…ents, tools, and commands, and agentRuns read from it
…rts, per-run usage, timeouts, attempts, and a budget code
… follow their parent, tool-mode children get ctx.harness and share the turn budget, and ctx.harness.state shares state by key
…yed children, and routed agents that take steers, late joins, and turn hooks
…g, for the parent's user and keys, with usage, budget, waits, and events in the parent run
@changeset-bot

changeset-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: dc5aede

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 7 packages
Name Type
@tanstack/ai-harness Minor
@tanstack/ai-acp Patch
@tanstack/ai-code-mode Patch
@tanstack/ai-harness-cli Patch
@tanstack/ai-mcp Patch
@tanstack/ai-sandbox Patch
@tanstack/ai-skills 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 18, 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: b871ff41-1291-470c-8182-16c2048e2e57

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 18, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit d75e0ce

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 5s View ↗
nx run-many --targets=test:coverage --projects=... ✅ Succeeded 1s View ↗
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 4s View ↗

☁️ Nx Cloud last updated this comment at 2026-10-18 08:53:07 UTC

@pkg-pr-new

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

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-acp

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

@tanstack/ai-angular

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

@tanstack/ai-anthropic

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

@tanstack/ai-bedrock

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

@tanstack/ai-byteplus

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

@tanstack/ai-claude-code

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

@tanstack/ai-client

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

@tanstack/ai-cloudflare

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-snippets

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

@tanstack/ai-codex

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

@tanstack/ai-cohere

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

@tanstack/ai-compaction

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

@tanstack/ai-devtools-core

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

@tanstack/ai-durable-stream

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-grok-build

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

@tanstack/ai-groq

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

@tanstack/ai-harness

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

@tanstack/ai-harness-cli

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-daytona

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

@tanstack/ai-isolate-e2b

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-isolate-quickjs-bun

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

@tanstack/ai-llmgateway

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

@tanstack/ai-lovable

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

@tanstack/ai-mcp

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

@tanstack/ai-memory

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

@tanstack/ai-mistral

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

@tanstack/ai-octane

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

@tanstack/ai-ollama

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

@tanstack/ai-ollaya

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

@tanstack/ai-openai

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

@tanstack/ai-opencode

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

@tanstack/ai-openrouter

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

@tanstack/ai-perplexity

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

@tanstack/ai-persistence

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-reactor

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

@tanstack/ai-remix

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

@tanstack/ai-sandbox

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

@tanstack/ai-sandbox-blaxel

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

@tanstack/ai-sandbox-boxd

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

@tanstack/ai-sandbox-cloudflare

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

@tanstack/ai-sandbox-daytona

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

@tanstack/ai-sandbox-docker

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

@tanstack/ai-sandbox-e2b

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

@tanstack/ai-sandbox-local-process

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

@tanstack/ai-sandbox-railway

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

@tanstack/ai-sandbox-sprites

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

@tanstack/ai-sandbox-upstash-box

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

@tanstack/ai-sandbox-vercel

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

@tanstack/ai-skills

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-typesafe

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

@tanstack/ai-utils

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

@tanstack/ai-vercel-gateway

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

@tanstack/ai-vertex

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/ai-worldlabs

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

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

@tanstack/svelte-ai-devtools

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

commit: dc5aede

@github-actions

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.60% (0.00) 71.10% (0.00) 78.24% (0.00) 84.54% (0.00) 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 93.46% (-0.07) 87.62% (+0.09) 93.56% (0.00) 95.21% (-0.03) ok
ai-harness-cli 90.81% (0.00) 82.69% (0.00) 86.48% (0.00) 91.91% (0.00) 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.00) 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.66% (0.00) 91.32% (0.00) 97.09% (0.00) 98.52% (0.00) 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

@github-actions
github-actions Bot requested a review from jherr October 18, 2026 11:08
@github-actions github-actions Bot added the waiting-on: maintainer The ball is in the maintainers’ court label Oct 18, 2026
@tombeckenham
tombeckenham added this pull request to stack #1826 October 18, 2026 21:20
@AlemTuzlak
AlemTuzlak removed this pull request from stack #1826 October 19, 2026 09:07
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