Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
7e38742
feat(ai-persistence): session stores for inputs, credentials, a log, …
AlemTuzlak Oct 13, 2026
a0d68a3
test(ai-persistence): name each inbox id in the conformance case
AlemTuzlak Oct 13, 2026
ebc8bc8
fix(ai-persistence): read the sessions store through the persistence …
AlemTuzlak Oct 13, 2026
fd76129
feat(ai-harness): new package with the harness contracts and plugin API
AlemTuzlak Oct 13, 2026
693f440
feat(ai-harness): session engine and host, with the session split int…
AlemTuzlak Oct 13, 2026
c76d26d
feat(ai-harness): durable sessions on the session log
AlemTuzlak Oct 13, 2026
7d0952b
test(ai-harness): unit tests and E2E for the core harness
AlemTuzlak Oct 13, 2026
228f8e8
docs(ai-harness): harness guides, README, and changeset
AlemTuzlak Oct 13, 2026
d893b50
Merge remote-tracking branch 'origin/feat/persistence-session-stores'…
AlemTuzlak Oct 13, 2026
f0215a3
fix(ai-harness): restore split imports, type the session for hooks, a…
AlemTuzlak Oct 13, 2026
4f84cf4
test(ai-harness): keep only the core auth case, and use a type import…
AlemTuzlak Oct 13, 2026
5a8ee70
fix(ai-harness): keep the lockfile minimal, sort the e2e dependency, …
AlemTuzlak Oct 13, 2026
a6bf3e1
fix(ai-harness): import ./session/index so the published d.ts files r…
AlemTuzlak Oct 13, 2026
47ea57a
feat(ai-skills): a harness plugin that makes each skill a slash comma…
AlemTuzlak Oct 14, 2026
7aeef8c
feat(ai-harness, ai-persistence): read the transcript of a child agen…
AlemTuzlak Oct 14, 2026
15d2b03
test(ai-harness): a fork does not read the child transcripts of its s…
AlemTuzlak Oct 14, 2026
cb42edc
fix(ai-persistence): loadChildTranscript keeps the card of a child wi…
AlemTuzlak Oct 14, 2026
9da1c07
docs(ai-harness): describe the card in session.agentTranscript
AlemTuzlak Oct 14, 2026
d915356
docs(ai-harness): show what a child agent wrote after a reload
AlemTuzlak Oct 14, 2026
dab3fc9
feat(ai-acp): serve a harness as an ACP v2 agent (#1758)
AlemTuzlak Oct 14, 2026
170879e
feat(ai-harness, ai-persistence, ai-skills): onEvent and onMedia call…
AlemTuzlak Oct 14, 2026
74c4c22
feat(ai-harness): serve sessions over HTTP and WebSocket, with a type…
AlemTuzlak Oct 15, 2026
ed590e4
feat(ai-harness): session.onQuestion, onAuthRequired, onMedia, and on…
AlemTuzlak Oct 15, 2026
d326802
feat(ai-harness): first-party plugins in @tanstack/ai-harness/plugins…
AlemTuzlak Oct 15, 2026
634ac2f
fix(ai-harness, ai-persistence): pass inputs to the instance that own…
jherr Oct 15, 2026
340cda7
feat(ai-code-mode): codeMode() harness plugin in @tanstack/ai-code-mo…
AlemTuzlak Oct 15, 2026
a8b7bf5
feat(ai-harness): goal() plugin that keeps working until a judge says…
AlemTuzlak Oct 15, 2026
0fef405
feat(ai-harness): build a harness into a worker artifact, and run it …
AlemTuzlak Oct 15, 2026
ea75cbb
feat(ai-harness): providerKeys() plugin lets each user connect their …
AlemTuzlak Oct 15, 2026
3e07156
fix(ai-harness): return done from the subagent start binding
jherr Oct 15, 2026
cf2d558
feat(ai-harness-cli): run a harness from the terminal, with optional …
AlemTuzlak Oct 15, 2026
17394d5
fix(ai-harness, ai-persistence, ai-acp, ai-skills): review fix pass o…
tombeckenham Oct 16, 2026
f4ffb64
refactor(ai-harness): discriminate Receipt, InputSettlement, MediaRec…
tombeckenham Oct 16, 2026
95ce6c0
docs(ai-harness, ai-persistence): match the docs to the fix pass
tombeckenham Oct 16, 2026
ca945af
feat(ai-harness)!: one session.send() replaces prompt, steer, followU…
tombeckenham Oct 16, 2026
5e94d3a
docs(ai-harness): send() on every harness page, and the plugin sessio…
tombeckenham Oct 16, 2026
7eea102
Merge origin/feat/harness-core: transport, plugins, CLI, worker, goal…
tombeckenham Oct 16, 2026
3f20909
fix(ai-harness, ai-persistence): review follow-ups on the send() push
tombeckenham Oct 16, 2026
59bc072
fix(ai-harness): close only the session a call opened, and report ref…
tombeckenham Oct 16, 2026
11a42e9
fix(ai-harness, e2e): lint the view receipt helper, and keep the e2e …
tombeckenham Oct 16, 2026
c2b505b
feat(ai, ai-harness, ai-persistence): credential store moves to byok,…
tombeckenham Oct 16, 2026
757a43d
feat(ai, ai-harness, ai-mcp): out-of-band tool runs, client appends, …
jherr Oct 16, 2026
c3d9d04
Merge origin/main into feat/harness-core
AlemTuzlak Oct 16, 2026
b7f6a8e
feat(ai, ai-persistence): keep approved edits raw until the final che…
AlemTuzlak Oct 16, 2026
d7e490a
feat(ai-harness): coding tools in @tanstack/ai-harness/plugins/coding…
AlemTuzlak Oct 16, 2026
91c36c3
feat(ai, ai-persistence, ai-harness, ai-mcp, ai-acp, ai-sandbox, ai-u…
AlemTuzlak Oct 20, 2026
5b946ea
fix(ai-harness-cli): give the ui view every session method
AlemTuzlak Oct 21, 2026
4f7a7a1
fix(ai-harness): read session view approvals in permissions()
AlemTuzlak Oct 21, 2026
66dc3c2
Merge main into feat/harness-core
AlemTuzlak Oct 21, 2026
51c7dbc
test(e2e): answer a harness approval with the session view shape
AlemTuzlak Oct 21, 2026
a4db92f
feat(ai): pass a debug option to the model calls of child agents
AlemTuzlak Oct 21, 2026
fdc8ad2
feat(ai-harness): send harness warnings and errors to a host logger
AlemTuzlak Oct 21, 2026
6731d11
feat(ai-harness): PermissionCallCapability for tool calls that a plug…
AlemTuzlak Oct 21, 2026
eaa9853
feat(ai-harness): child agents log through the host logger
AlemTuzlak Oct 21, 2026
d6a3af3
feat(ai-harness): FileChanged events with a unified diff from the cod…
AlemTuzlak Oct 21, 2026
d999b17
feat(ai-sandbox-docker): copy a localSource folder into the container
AlemTuzlak Oct 21, 2026
316dbbc
feat(ai-code-mode): code mode runs the coding tools under the permiss…
AlemTuzlak Oct 21, 2026
aceebf0
feat(ai-computer-use, ai-harness): computerUse() harness plugin in @t…
AlemTuzlak Oct 21, 2026
2705ae5
feat(ai-mcp, ai-code-mode): plugin warnings go to the host logger
AlemTuzlak Oct 21, 2026
0d4321c
docs(harness, sandbox): copy a local folder into a Docker sandbox
AlemTuzlak Oct 21, 2026
d464fb9
test(e2e): a code mode program edits a file after an approval
AlemTuzlak Oct 21, 2026
a36748d
feat(ai-harness): snapshots() sends FileChanged for the files of bash
AlemTuzlak Oct 21, 2026
7adbb35
Merge remote-tracking branch 'origin/feat/harness-core' into t3/fdfd3164
AlemTuzlak Oct 21, 2026
ba0dcb9
test(e2e): FileChanged for a write_file edit and a bash write
AlemTuzlak Oct 21, 2026
681a13c
docs(harness): show live diffs, and answer permissions() approvals fr…
AlemTuzlak Oct 21, 2026
74efa98
docs(harness): run the coding tools in a code mode program
AlemTuzlak Oct 21, 2026
46136ef
fix(ai-code-mode): write the aroundCalls hook type inline
AlemTuzlak Oct 21, 2026
e4ceb6d
feat(ai-harness, ai-skills): host plugins get the host logger
AlemTuzlak Oct 21, 2026
7283725
docs(harness): send the harness warnings to your own logger
AlemTuzlak Oct 21, 2026
1398980
fix: one @google/genai for ai-vertex and ai-gemini in the lockfile
AlemTuzlak Oct 21, 2026
787718a
fix(ai-harness): link a permissions() approval in the view to its too…
AlemTuzlak Oct 21, 2026
89209cd
Merge origin/main into feat/harness-core
tombeckenham Oct 22, 2026
c0b3960
Merge origin/feat/harness-core
tombeckenham Oct 22, 2026
9d999eb
refactor(ai-harness, ai-client): drop fallbacks for the now-required …
tombeckenham Oct 22, 2026
b2e7502
chore: release ai-harness and ai-harness-cli as 1.0.0 with the rest
tombeckenham Oct 22, 2026
228d9c1
fix(ai-harness): view.ready settles when the session closes during it…
AlemTuzlak Oct 22, 2026
142b8f2
fix(ai-sandbox-docker): resume gives null only for a missing container
AlemTuzlak Oct 22, 2026
81cada2
fix(ai-harness): undo, redo, and revert send FileChanged for the file…
AlemTuzlak Oct 22, 2026
b6ca708
fix(ai-harness): keep the user message of a turn that stops before it…
AlemTuzlak Oct 22, 2026
79a9466
test(ai-harness): give the inner-failure harnessText test a threadId
AlemTuzlak Oct 22, 2026
c121f26
fix(ai-harness): a forwarded command ends with the owner's result or …
tombeckenham Oct 22, 2026
d7a9520
fix(ai-persistence): check a durable resume after the whole onConfig …
AlemTuzlak Oct 22, 2026
9ba963f
fix(ai-harness): run the harness middleware on ctx.model.chat calls
AlemTuzlak Oct 22, 2026
88196ec
fix(ai-compaction): add a middleware option to conversationSummarizer
AlemTuzlak Oct 22, 2026
efac8a4
fix(ai-compaction): drop a background summary that another model prep…
AlemTuzlak Oct 22, 2026
c918220
fix(ai-harness): run the continuation of a recovered resolve
AlemTuzlak Oct 22, 2026
7d5eeb0
fix(ai-harness): keep the text that a cancelled turn streamed
AlemTuzlak Oct 22, 2026
ff0dd8c
fix(ai-harness): end the text message and the run of a cancelled turn
AlemTuzlak Oct 22, 2026
7f0cb11
fix(ai-harness): show idle in host.events() after a turn that a steer…
AlemTuzlak Oct 22, 2026
ff9707d
fix(ai-anthropic): move outputSchema keywords that Anthropic rejects …
AlemTuzlak Oct 22, 2026
2794a50
fix(ai-anthropic): report the 1-hour part of a cache write as cacheWr…
AlemTuzlak Oct 22, 2026
bbfd225
fix(ai-anthropic): warn once when chat({ reasoning }) is ignored for …
AlemTuzlak Oct 22, 2026
cf91c73
fix(ai-anthropic): also move an outputSchema not into the description
AlemTuzlak Oct 22, 2026
addd7b9
fix(openai-base): drop msg_ and fc_ item ids on replay of a message w…
AlemTuzlak Oct 22, 2026
2e523b1
fix(ai-cloudflare): keep the reply when the usage trailer has no choi…
AlemTuzlak Oct 22, 2026
5fb7da0
fix(ai): put the model text on a schema failure, and give a non-JSON …
AlemTuzlak Oct 22, 2026
840f9c9
fix(ai-client): send addToolResult through the generic interrupt when…
AlemTuzlak Oct 22, 2026
ef1fbde
fix(ai-persistence): check "not due yet" on a live log so slow stores…
AlemTuzlak Oct 22, 2026
72224eb
feat(ai-sandbox): let each sandbox file call take an abort signal
AlemTuzlak Oct 22, 2026
5912890
feat(ai-harness): add a beforeWrite workspace hook that can refuse a …
AlemTuzlak Oct 22, 2026
e93a065
fix(ai-harness): the duplicate-input check ignores the key order of t…
AlemTuzlak Oct 22, 2026
4e939ba
fix(ai-harness): a steer that can't join another sender's turn gets a…
AlemTuzlak Oct 22, 2026
bd1b37d
feat(ai-harness): harness.input.accepted carries the input's user mes…
AlemTuzlak Oct 22, 2026
3524eb2
feat(ai-harness): TurnInfo names the model and the attempt, and a sna…
AlemTuzlak Oct 22, 2026
1c9719a
feat(ai-harness): TurnInfo.sender names who sent the input, also the …
AlemTuzlak Oct 22, 2026
617c7d5
fix(ai-harness): the log keeps the name of a sender, not only its id …
AlemTuzlak Oct 22, 2026
1f9c8fc
fix(ai-harness): the run hydration carries the pending waits, so a re…
AlemTuzlak Oct 22, 2026
c9067d6
feat(ai-harness): a host.observe view reads how an input ended with s…
AlemTuzlak Oct 22, 2026
80b7536
docs(ai-harness): idle in a snapshot means no turn runs on this host,…
AlemTuzlak Oct 22, 2026
4541335
feat(ai-harness): export the abort reasons SHUTDOWN_REASON, RUN_CANCE…
AlemTuzlak Oct 22, 2026
40ae662
feat(ai-harness, ai-client): export HARNESS_EVENTS from ./client, and…
AlemTuzlak Oct 22, 2026
499bea9
feat(ai-harness): subagents.background false keeps the background fie…
AlemTuzlak Oct 22, 2026
f9c1ff0
feat(ai-harness): media.urlTtlMs sets how long a signed media URL works
AlemTuzlak Oct 22, 2026
41aff07
chore: changeset for the fifth findings list fixes
AlemTuzlak Oct 22, 2026
2106e60
fix(ai, ai-persistence): resume a waiting nested subagent from the st…
tombeckenham Oct 22, 2026
9ae0147
Merge main into feat/harness-core
AlemTuzlak Oct 22, 2026
2036c27
feat(ai-harness): onModelError can retry the turn on another adapter
AlemTuzlak Oct 22, 2026
92eba20
feat(ai-harness): middleware reads who made a model call from ctx.con…
AlemTuzlak Oct 22, 2026
5f085d3
feat(ai-harness): a plugin model pick with override: true wins over t…
AlemTuzlak Oct 22, 2026
0e6600a
feat(ai-harness): acceptInput checks, changes, or refuses each input …
AlemTuzlak Oct 22, 2026
f62fb9f
feat(ai): let a server tool read run capabilities
AlemTuzlak Oct 22, 2026
1344367
feat(ai-sandbox): pass the tool call signal to the workspace file calls
AlemTuzlak Oct 22, 2026
7ca770f
feat(ai-compaction): allow maxTokens, contextWindow and atTokens as f…
AlemTuzlak Oct 22, 2026
061b317
test(ai-harness): readers keep the full transcript after a compaction
AlemTuzlak Oct 22, 2026
49fb1ed
fix(ai-harness): keep the full transcript for readers after a compaction
AlemTuzlak Oct 22, 2026
2591eb1
feat(ai-persistence): archive a thread and hide it from lists
AlemTuzlak Oct 22, 2026
5844537
feat(ai): check a streamed outputSchema value against the schema
AlemTuzlak Oct 22, 2026
aae5b45
feat(ai-harness): outputSchema on session.send
AlemTuzlak Oct 22, 2026
4e75a7f
feat(ai-harness): erase the messages of one user from a thread
AlemTuzlak Oct 22, 2026
d8f98b5
feat(ai-harness): export HarnessRecord and document an encrypting Log…
AlemTuzlak Oct 22, 2026
ec69fa8
docs(ai-harness): say that an erase drops a compaction summary
AlemTuzlak Oct 22, 2026
b9c2510
chore: changeset for the fifth findings list asks
AlemTuzlak Oct 22, 2026
7e9b959
Revert "fix(openai-base): drop msg_ and fc_ item ids on replay of a m…
AlemTuzlak Oct 22, 2026
8f16c8a
fix(openai-base): drop replay item ids only when the message lost its…
AlemTuzlak Oct 22, 2026
5af9675
chore: correct the B13 changeset line
AlemTuzlak Oct 22, 2026
9436245
Merge remote-tracking branch 'origin/main' into feat/harness-core
AlemTuzlak Oct 22, 2026
cb53c39
docs(ai-harness): drop duplicate imports in shared snippet groups
AlemTuzlak Oct 22, 2026
1349c2f
Merge remote-tracking branch 'origin/main' into feat/harness-core
AlemTuzlak Oct 22, 2026
0b5f3e9
fix: repair the build and tests after the model sync
AlemTuzlak Oct 22, 2026
f22d0a0
chore: release ai-harness and ai-harness-cli as 0.1.0
AlemTuzlak Oct 22, 2026
9192f2b
docs(ai-harness): mark the harness as experimental; fix the e2e model…
AlemTuzlak Oct 22, 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
  •  
  •  
  •  
12 changes: 12 additions & 0 deletions .changeset/ai-harness-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@tanstack/ai-harness-cli': minor
---

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

- 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.
- `--serve` serves the session protocol over HTTP behind a bearer token. It signs media URLs with its own key, never with the token: `--media-key` or `HARNESS_MEDIA_KEY` (at least 32 bytes), or a new random key at each start. Without a set key, signed media URLs stop working after a restart. On a host without `access`, it also passes `openAccess`: the `user` of `runCli`, or `true` without one. So each request with the token runs as the user of the CLI.
- `--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.
- 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 and does not wait for a turn that ignores the cancel. The `signals` option takes another signal source, for example an `EventEmitter` in a test.
- `host` and `user` options let the CLI use your durable host and run as your user. Every mode opens its sessions as that user. On a host with `access`, `--serve` reads the user of each request with `getUser`, and `--mcp` does not start, because stdio has no login.
20 changes: 20 additions & 0 deletions .changeset/ai-harness-coding-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@tanstack/ai-harness': minor
---

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'` 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 `ownership.expiresAfterMs`: 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.moveToolCallToBackground()` 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. `session.jobs()` and `jobs()` of a `host.observe` view list the jobs that did not end (`JobInfo`: the job id, the thread, the command or tool name, who moved it, when it started, and `running` on this host). 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