Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b2126e5
feat(ai-harness): thread-state log records and their fold
AlemTuzlak Oct 17, 2026
a60085f
feat(ai-persistence, ai-harness): one persistence shape, every host h…
AlemTuzlak Oct 17, 2026
869b4a5
Merge branch 'feat/harness-lifecycle' into feat/harness-log-session
AlemTuzlak Oct 17, 2026
c4cafd3
feat(ai-harness): settings, config, plugin state, interrupted turns, …
AlemTuzlak Oct 17, 2026
96ce736
Merge branch 'feat/harness-lifecycle' into feat/harness-log-session
AlemTuzlak Oct 17, 2026
1a0962a
feat(ai-harness): one ownership epoch per log, renewed once per host,…
AlemTuzlak Oct 17, 2026
babeff6
feat(ai-harness): runs and interrupts live in the session log
AlemTuzlak Oct 17, 2026
07470f5
feat(ai-persistence, ai-harness): inputs from any machine through the…
AlemTuzlak Oct 17, 2026
0430d43
feat(ai-harness): createHarnessWorker, session.schedule, and step.sle…
AlemTuzlak Oct 17, 2026
c249782
test(e2e): a worker on another host runs a submitted input and a sche…
AlemTuzlak Oct 17, 2026
8e9797c
feat(ai-harness): one log per session tree, session.logState(), and a…
AlemTuzlak Oct 17, 2026
6d96156
feat(ai-harness): snapshots in the log store by size or time, retenti…
AlemTuzlak Oct 17, 2026
1d2300f
fix(ai-harness): export LogOwnedElsewhereError, and say that the log …
AlemTuzlak Oct 17, 2026
28b0800
docs(ai-harness, ai-persistence): durable sessions, one log per tree,…
AlemTuzlak Oct 17, 2026
a28f10c
ci: apply automated fixes
autofix-ci[bot] Oct 17, 2026
606fa8e
fix(ai-harness): check answers and cancels from submit and schedule a…
AlemTuzlak Oct 17, 2026
1dc6224
test(ai-harness): give polled checks 5 s, for slower CI coverage runs
AlemTuzlak Oct 17, 2026
f75cced
Merge remote-tracking branch 'origin/feat/harness-log-session' into f…
AlemTuzlak Oct 17, 2026
4c3d101
test(e2e): read the data lines of POST run streams that now carry an …
AlemTuzlak Oct 17, 2026
d73bc86
Merge branch 'feat/harness-lifecycle' into feat/harness-log-session
AlemTuzlak Oct 17, 2026
2eef71b
feat(ai, ai-harness, ai-mcp): one redaction sink, the final tool resu…
AlemTuzlak Oct 20, 2026
3b2cb18
fix(ai-persistence): keep the harness of the owner or the first offer…
AlemTuzlak Oct 19, 2026
327bbfb
fix(ai-harness): check the harness in host.submit, and throw when a c…
AlemTuzlak Oct 19, 2026
2700188
fix(ai-harness): prune settled state from the thread fold, and change…
AlemTuzlak Oct 19, 2026
70812c8
fix(ai-harness): take only JSON values from step.do
AlemTuzlak Oct 19, 2026
3624e0f
fix(ai-harness): back off a failing log in the worker, free the slot …
AlemTuzlak Oct 19, 2026
a60f629
test(ai-harness): skip a build-stub test that times out under load
AlemTuzlak Oct 19, 2026
7ec02cb
chore(changeset): note the review fixes of the log session
AlemTuzlak Oct 19, 2026
7e96789
fix(ai-harness): need media only on a host with ownership, and warn o…
AlemTuzlak Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions .changeset/ai-byok-credential-cas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@tanstack/ai': minor
---

`CredentialStore` can now do compare-and-set writes. It has two new optional methods, `getVersioned` and `setIf`, with the same shape as the pair on `MetadataStore`. A store without them still works. `memoryCredentialStore` has both.

The new `readCredentialEntry` in `@tanstack/ai/byok/server` reads a credential like `readCredential`. It also tells you which scope had the credential (the user or the tenant) and gives its revision when the store has `getVersioned`.

`scrubSecrets` from `@tanstack/ai/byok` now also removes the JSON-escaped and URL-encoded forms of each secret. It skips a secret that is shorter than 6 characters, so a short value does not remove common words.

`Credential` has a new type, `secret_answer` (`{ value, expiresAt }`). The harness keeps a secret answer to a question there for a short time and deletes it once read. Leave this type out of a list of connected accounts. `memoryCredentialStore().list` returns its `expiresAt`.
8 changes: 4 additions & 4 deletions .changeset/ai-harness-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

Add `@tanstack/ai-harness-cli`: `runCli(harness)` runs a harness from the terminal.

- Line mode reads one message or `/command` per line, answers approvals and questions, hides a secret answer while the user types, and sends `@path` files. Pass `ui` to show your own screen on a session view instead.
- `-p "prompt"` runs one prompt for scripts and CI, and `--output ndjson` prints every AG-UI event.
- Line mode reads one message or `/command` per line, hides a secret answer while the user types, and sends `@path` files. In a terminal, `/cancel` and a message typed while a turn runs act at once: the message joins the running turn. When a turn waits, each next line answers one wait: `y` or `n` for an approval (`once`, `always`, or `reject` for an approval of `permissions()`), and an answer read with the schema for a question. Pass `ui` to show your own screen on a session view instead.
- `-p "prompt"` runs one prompt for scripts and CI, and `--output ndjson` prints every AG-UI event. `-p` exits with code 3 (`EXIT.needsAction`) at the first wait of the turn: an approval, a question, or a sign-in. The exit code for this case changed from 2 to 3. A script that checks for exit code 2 must check for 3.
- `--serve` serves the session protocol over HTTP behind a bearer token.
- `--acp`, `--mcp`, and `--dashboard` load `@tanstack/ai-acp`, `@tanstack/ai-mcp`, and `@tanstack/ai-dashboard` only when you use the flag. A missing package, or one without harness support, fails that flag with a clear message.
- `--acp` and `--mcp` load `@tanstack/ai-acp` and `@tanstack/ai-mcp` only when you use the flag. A missing package, or one without harness support, fails that flag with a clear message. Any other error while the package loads is reported, not hidden. The `--dashboard` flag was removed.
- In every mode, the first SIGINT or SIGTERM cancels the running turn. A second one, or one while no turn runs, stops the CLI, and `runCli` resolves to 130 (SIGINT) or 143 (SIGTERM). The CLI closes a host that it made with `recoverable: true`, or releases its session handle on your own host. The `signals` option takes another signal source, for example an `EventEmitter` in a test.
- `host` and `principal` options let the CLI use your durable host and run as your user. Every mode except `--dashboard` opens its sessions as that principal. On a host with `access`, `--serve` reads the user of each request with `identify`, and `--mcp` does not start, because stdio has no login.
- `host` and `principal` options let the CLI use your durable host and run as your user. Every mode opens its sessions as that principal. On a host with `access`, `--serve` reads the user of each request with `identify`, and `--mcp` does not start, because stdio has no login.
15 changes: 11 additions & 4 deletions .changeset/ai-harness-coding-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@

Add coding tools in the new Node entry `@tanstack/ai-harness/plugins/coding`:

- `workspaceTools({ root })` gives `read_file` (text pages, images, PDFs), `write_file`, `edit_file` or `patch` (`editStyle`), `list_files`, `grep`, `bash` (with `background`), `webfetch`, and `websearch` (with a `SearchProvider`). With `permissions()`, edits, `bash`, and `webfetch` ask first, and the rules also apply to the real path of a link. `outside: 'ask'` asks before a path outside `root`. The `cwd` thread setting gives each thread its own working folder.
- A `WorkspaceBackend` runs the tools. `hostBackend` is the default. On Windows, commands do not run a program from the working folder by its name.
- `snapshots()` adds `/undo` and `/redo`, which restore only the files of the turn (git 2.26 or newer), file restore for `session.revert()`, and a diff for each model step.
- `formatter()` formats each written file with the formatter of the project. With `permissions()`, each run asks first.
- `workspaceTools({ root })` gives `read_file` (text pages, images, PDFs), `write_file`, `edit_file` or `patch` (`editStyle`), `list_files`, `grep`, `bash` (with `background`), `webfetch`, and `websearch` (with a `SearchProvider`). With `permissions()`, edits, `bash`, and `webfetch` ask first, and the rules also apply to the real path of a link. `outside: 'ask'` makes a path outside `root` need an approval. `permissions()` decides outside paths and the real paths of links before the call runs, so a tool never asks while it runs. The `cwd` thread setting gives each thread its own working folder.
- `workspaceTools({ workspace: { bind, reattach? } })` gives each thread its own workspace. The root of the workspace is the boundary of every tool. The session log keeps where the workspace is (`harness.workspace.bound`). On another host, `reattach` gives the workspace back (for example a resumed sandbox), or `'pinned'` or `'gone'`. A gone workspace sends `tanstack/workspace-tools:stranded`. With this plugin first, `snapshots()` and `formatter()` use the workspace of each thread, and their `root` is optional.
- `createHarnessHost({ ownership: { hostId } })` gives a host a stable id. Then a thread that uses a folder on that host is pinned to it, and other hosts refuse its tool calls. Without `hostId`, threads are not pinned. A host with `hostId` sends a heartbeat to the log store, and its pins lapse when no heartbeat came for `ttlMs`: then another host reattaches its threads. `host.unpin({ harness, threadId })` unpins a thread from a host that does not come back, once its ownership expired.
- `workspaceRoot(base, ...parts)` gives a folder in `base` for a tenant and a thread id. It encodes each part, so a thread id from a client cannot leave `base` or reach another thread.
- Commands, `rg`, `git`, and the formatters get only a short list of variables of the server process, plus the `env` of the backend call. Git runs without the hooks and the `fsmonitor` of the project.
- The tools resolve links with `realpath` under the file lock, and fail when a backend cannot resolve a path. A write fails when the file changed after the tool read it in the same chat run, so a lead and its subagents do not lose each other's edits. The file locks are shared by the sessions of a host, by the sandbox (or host) and the real path of the file.
- Background jobs: at most 8 per session (`maxJobs`), the newest 1 MiB of output per job, and the events `tanstack/workspace-tools:job-started`, `job-output`, and `job-ended`. A running command that `session.background()` moves counts as a job, and its process id goes to the log. A job that an earlier host left running is stopped at the next tool call, with a note to the model. When the backend cannot check the job (for example with no `ps`), the log keeps it, and the note says that it can still run. The wake turn of a job runs as the user who started it. The `bash` spill file has `[redacted]` in place of each secret of the session.
- `read_file` sends images and PDFs up to 256 KB. `webfetch` gives the model the first 256 KB of a page.
- A `WorkspaceBackend` runs the tools. `hostBackend` is the default. `realpath` is required. `spawn` can give a `jobId`, `exec` can give one with `onStart`, and `killJob`, `binding`, and `close` are optional. `killJob` rejects when it cannot check that the process is the job. On Windows, commands do not run a program from the working folder by its name.
- `snapshots()` adds `/undo` and `/redo`, which restore only the files of the turn (git 2.26 or newer), file restore for `session.revert()`, and a diff for each model step. Each thread of each tenant has its own shadow repository, which never stores `.env` files. A step keeps only the files of its own run. The steps and the undo point are in the session log, so another host with the same repository can undo. A revert uses the last 500 steps. `host.sessions.delete` removes the repository of the thread, and `purgeSnapshots()` does it from your own code. A failed snapshot sends `tanstack/snapshots:failed`.
- `formatter()` formats each written file with the formatter of the project. With `permissions()`, the rules decide the formatter with the call that writes, before that call runs: one approval covers both, and the formatter runs after the write. An `always` answer is tied to a hash of the formatter config files. A write of a formatter config below the root, or under `node_modules`, asks each time, and an `always` answer saves no rule for that formatter run.
- `WorkspaceHooks` lets other plugins run code after a read or a write.

`@vscode/ripgrep` and `turndown` are optional peer dependencies.
Loading
Loading