A fuzz test reads its input through std.testing.Smith, which reads a slice
as a little-endian u32 length and then that many bytes. The inline seeds of
the older fuzz tests were plain strings, so each lost its first four bytes
to the length: "one" replayed as an empty input, and "split every field"
read "spli" as a length. The fx tests now wrap their seeds in
fuzz_seed.slices, which writes that length first. The session manager
imports nothing from fx, so its two targets keep their seeds as saved
inputs in tests/fuzz/corpus.
- `zig build fuzz` builds only the fuzz tests listed in `tests/fuzz/targets.json`. `-Dfuzz-target=<name>` picks one target, and `--fuzz=N` searches for new failing inputs.
- `zig build test` replays each target's saved inputs from `tests/fuzz/corpus/<target>/` on every pull request.
- A nightly Fuzz workflow fuzzes every target for about 20 minutes, uploads any failing input, and opens or updates an issue for that target. A pull request that changes the fuzzing setup fuzzes each target for two minutes.
- The shell parity tests run every command form fx runs without review through sh, dash, bash, and zsh, with and without `extendedglob`, and check that each one runs exactly as fx parsed it. The `shell_parity` target checks generated commands the same way.
- Add models, checkers, and Bun drivers for ACP permission modes and
speed, launch flags, durable libfx, the MCP-v2 engine and the MCP
host, /usage, the shell tool and git admission, the shell snapshot
and terminal lifecycle, the libfx provider credential edge, the
indexer's grep, file picker, and snapshot reuse, and the session
rollout.
- Add tests/tla/lib/ and tests/tla/drivers/lib/ so every checker and
driver finds TLC and Java the same way, gives each TLC run its own
temp folder, runs the binary from this checkout, and keeps sound off.
- Add tests/tla/mcp/rules.md, the MCP specification rules the MCP-v2
models cite by ID.
- Mark the models that describe the next MCP-v2 version, and run their
drivers only with --next or FX_TLA_NEXT_MCP=1.
- Point the /usage and MCP trace comments in src/ at their models, and
update tests/tla/README.md, tests/AGENTS.md, and CONTRIBUTING.md.
- Move `drive.mjs`, `drive-agents.mjs`, `agent-process.mjs`, and
`graph.mjs` from `tests/tla/sdk/` to `tests/tla/drivers/`. They read the
state graphs that `tests/tla/sdk/check.sh` writes.
- Point CI, `AGENTS.md`, `sdk/AGENTS.md`, and the `tests/tla` notes at the
new paths.
- Move the session manager specs, trace checkers and design record, the
indexer refresh model, and the libfx durable session model and its Bun
drivers into `tests/tla/`, so `src/` and `sdk/` hold no TLA+ files.
- Add `tests/tla/indexer/check.sh`, which runs every refresh config and
requires its expected verdict.
- Point code comments, CI, `AGENTS.md`, `sdk/AGENTS.md`, and
`CONTRIBUTING.md` at the new paths, and document the models in
`tests/tla/README.md`.
The guide said two more workflows run on a pull request, but the
Examples workflow and the Shell command overhead benchmark also
report. Name them as examples instead of giving a count, and say that
only the five required checks block a merge.
Put commit signing before pushing, since pushing first leaves unsigned
commits to rewrite. Describe Startup Latency without a command count,
because the benchmark measures five fx commands plus a baseline, and
limit the Shellcheck row to the scripts the job lists. Say that a
validate-only release still uploads workflow artifacts but skips the
tag, GitHub Release, CDN, and update channel.
Register an E2E file in macos-platform-tests.json only when it
branches on the platform, and stop sending configuration changes to
CONTRIBUTING.md now that its configuration reference is gone. Tie the
companion docs PR in AGENTS.md to push access on the docs repository,
which organization membership alone doesn't grant.
In the README tree, list MCP under core/, where the default client
lives, and describe src/mcp/ as the opt-in v2 engine.
The fx.sh manual is built from a private repository, so outside
contributors can't edit it. Ask them to put the exact change each
affected page needs in a Docs section of the PR description. In
AGENTS.md, tell members of the vercel-labs organization to update the
manual in a companion pull request, and stop naming the private
repository in both files.
Only maintainers can push branches, add labels, including ci:macos,
or start workflow runs. Say that directly instead of naming GitHub
access levels, and give the outside-contributor path at each of those
steps.
CONTRIBUTING.md had grown into a product reference: MCP lifecycle
details, ACP extensions, the auto-mode review rules, and config keys,
most of which AGENTS.md and the fx.sh/docs manual already cover.
Rebuild it around the contribution workflow: setup, testing, pull
requests, labels, CI, docs, and releases. Every step that needs write
or triage access now says what an outside contributor can do.
Document that main accepts only signed commits, that an older Zig
fails inside build.zig with an error that doesn't name the version,
and how to try a change against an empty profile. Drop stale claims
about a sandbox project key, a tests/e2e/tapes directory, and a PGSO
local reproduction command.
In the README, draw the source tree with branch lines and keep fx ask
and fx acp on one line in the surface table.
main moved to Zig 0.17. The branch keeps its deletion of
file_index_cache.zig, ignored_dirs.zig and workspace_files.zig, whose
only changes on main were 0.17 renames, and keeps its own side of
grep_search.zig and glob_files.zig. AGENTS.md takes main's refresh plus
the indexer and safe git notes.
The branch's code moves to the 0.17 names: std.Io.Dir.path,
Allocator.print, std.mem.print, builtin.target and @backingInt. The
wildmatch test builds its repeated strings with a local helper because
the indexer cannot import text_utils, and two folder selection test
calls gain the try that 0.17 requires.
AGENTS.md told agents to add feature descriptions and usage examples
to README.md for every user-facing change, so feature work kept growing
the README. Point those docs at --help and the fx.sh/docs manual
instead, say how to update the manual with or without access to
fx-web, and limit README edits to statements that become false or new
top-level capabilities.
Drop the release step that updated the README's install version, which
is no longer pinned, and point the CONTRIBUTING.md checklist at the
documentation rule.
Lead with what fx is, its features, install, and a quick start, and
leave reference detail to fx.sh/docs. Point the custom model
connections link in CONTRIBUTING.md at the docs page, since the README
section is gone.
- Add src/core/indexer/, which reads gitignore rules, repository layout, and git config as files and never starts a process.
- Match git's ignore decisions for nested .gitignore files, info/exclude, and the global excludes file, including include and includeIf config.
- Report the filter drivers a repository's own config defines, and fail closed when that config cannot be read.
- Check in CI that outside code imports only indexer.zig and that the indexer imports nothing that can start a process.
Build fx with Zig 0.17.0. build.zig.zon requires it, every workflow
installs it, and PGSO qualification uses LLVM 22.1.8 to match.
Zig 0.17 removes `**`, `errdefer |err|`, `@cImport`, and the old type
info field lists. The source moves to `@splat`, a comptime
`text_utils.repeat` helper, split cleanup functions, and the new
field_names and field_types arrays. The Node-API addon translates
node_api.h with a build step. Test fakes checked with `@hasDecl` become
pub, because `@hasDecl` no longer sees private declarations.
`zig build test` gives the test binary its fx and source paths through
a generated test_paths module, since the build no longer exposes install
paths for environment variables.
Zig 0.17's SafeAllocator grows a small allocation in place only when it
sits at the end of a bucket, and buckets are never reused, so an
allocation-failure sweep could count a different number of allocations
on each run. Sweeps and allocation-count tests now use
testing_allocator.no_resize, which never resizes in place.
allocRemaining now treats its limit as inclusive. readFileToEnd keeps
rejecting a file of exactly max_bytes by reading through
appendRemaining. Direct allocRemaining callers for remote responses now
accept a body exactly at their limit.
Zig 0.17's memset stores a full vector per iteration, so the Linux
memset replacement is removed. macOS release binaries now require macOS
15, Zig 0.17's default minimum.
Applying a label takes triage access and dispatching a workflow takes
write access, so contributors from outside the organization could not
follow the PR rules. AGENTS.md also told agents to hold such PRs in
draft until a maintainer added the label.
Keep the type label rule for contributors who can manage labels. For
outside contributors, a maintainer applies the label during review, and
they ask in the PR when they need the macOS checks, PGSO, or another
on-request run.
- Show the first frame before reading a Keychain-backed fx login or stored key on macOS. A prompt sent before the login loads waits for it, then sends.
- Load skills after the first frame. Skill menus fill in when the catalog lands, and skill discovery issues are reported then.
- Check the other sign-in sources after the first frame when a credential is already active, so a launch with an environment key no longer loads Security.framework before drawing.
- Wait at most 1 ms for input before drawing the first frame, instead of 8 ms.
- Stop waiting up to 200 ms for terminals that ignore the background color query. The startup probe now ends at the device attributes reply.
- Show "skills: loading" in the full-transcript session details while skills are still loading.
- Add benchmarks/first_frame.py to measure time to the first frame and compare builds under the same load.
The shard guard treats platform() from node:os as a platform branch, but
the detector only matched process.platform, so a helper written that way
could skip macOS. Match both, list the helper rule in the detector's
header, and say "reads the platform" in the docs.
The dispatch fetch depth used `event == dispatch && 0 || 2`. Expressions
treat 0 as falsy, so every event got depth 2, origin/main was never
fetched, and a manual dispatch failed at detect. Put the dispatch case
last so it evaluates to 0, and capture the merge base before calling
the detector so a missing base fails with git's own error.
The detector ignored shared E2E helpers, so a change to a platform
branch in tests/e2e/tmux-helpers.ts could skip macOS. Treat a helper
that reads process.platform on either side of the change as a macOS
input.
A manual macOS dispatch now compares the branch with its merge base on
main, so it runs at least the checks its pull request run would and
cannot replace a native addon failure with a weaker pass.
The setup-zig action fails if setup-zig stops pointing the Zig cache at
the workspace directory the cache steps save and restore.
The docs now say that Binary Size smoke-tests every target, that the
platform list covers darwin and linux branches, and that only the Linux
Zig cache actions skip saving on pull requests.
The macOS detector now fails the check when it cannot read either
revision or the platform test list, instead of reporting that macOS is
not needed. It also treats Linux and BSD code paths as platform code,
because macOS takes the other side of a Linux branch. Windows-only
branches still skip macOS. Replaying the last 150 commits on main moves
macOS runs from 72 to 76.
The macOS platform E2E files now run in two weighted shards that share
one baseline-CPU build. tui-performance and tui-resume join the list
because they branch on the platform.
Linux Zig jobs restore a cache keyed by the Zig version and build files,
and only runs on main save it. Pull request runs no longer add cache
entries that push main's out of the quota. The macOS jobs never run on
main, so their Zig cache is off.
Build & Test now also requires the SDK jobs, which Publish libfx relies
on. Manual dispatches get their own concurrency group so they cannot
cancel the push run on main. The N-API build pins Node 24, Binary Size
smoke-tests every target, and the shellcheck job syntax-checks each
script instead of only the first.
- Run the full deterministic E2E suite on Linux in four shards on every pull request, plus the MCP conformance baseline, all against one shared build of fx.
- Run macOS arm64 unit tests and platform E2E files only when a change touches macOS-specific code, build.zig, macOS signing, or the native SDK addon, or carries the ci:macos label. Otherwise the macOS arm64 check passes without a macOS runner.
- Remove the four-platform Full CI workflow and the separate cross-target compile jobs.
- Smoke-test the Linux arm64, macOS x86_64, and macOS arm64 binaries in Binary Size, and reuse the base build for later pushes to the same pull request.
- Build the SDK WASM surfaces and Node-API addons once and share them across the SDK test jobs.
- Run PGSO on pull requests only when the PGSO pipeline changes, and keep release PGSO evidence for 90 days.
- Run the long-turn memory, libfx runtime, and live ACP jobs only on request.
- Limit libfx publishing to an archive check, an install smoke test, and a registry byte comparison.
- Add a warning when an E2E file passes only on retry.
- Remove the unused Prepare Release workflow and document the new CI rules.
- Keep tools from MCP servers the client supplies loaded on every turn. A server entry can opt out with `_meta.fx.alwaysLoaded: false`.
- Run a model call to a live MCP tool that is not loaded yet through the normal MCP permission path instead of rejecting it.
- Accept a `session/prompt` with `_meta.fx.steer` while a turn is running. The turn picks it up at its next safe point, echoes it as a `user_message_chunk`, and answers it with `absorbed`, or `cancelled` with `dropped` when the turn is cancelled.
- Accept `systemPrompt` text blocks on `session/new` in append mode, advertise `sessionCapabilities.systemPrompt`, reject invalid blocks with `-32602`, and restore the prompt on `session/load` and `session/resume`.
- Leave out terminal-specific instructions when `initialize` sets `_meta.fx.terminal: false`.
- Mark discovery calls such as `capability_search`, `mcp_select_tool`, and `read_tool_result` with `_meta.fx.toolCall.internal`, and add the MCP server, original tool name, and server title to MCP tool calls.
- Serve MCP servers over the ACP connection through `type: "acp"` entries with a `serverId`, advertised as `mcpCapabilities.acp`. Unanswered `mcp/message` requests get `$/cancel_request` when the turn is cancelled or times out.
- Retry a client-hosted MCP server on the next turn when a cancelled turn interrupted its first connection.
- Use the `cwd` on `session/new`, `session/load`, and `session/resume` as the session workspace, and start project MCP servers in it.
- Include profile MCP servers from `~/.fx/mcp.json` in an ACP session only when it sets `_meta.fx.profileMcpServers`.
- Replay absorbed steering and MCP tool call titles and identity on `session/load`.
- Manual /compact, automatic compaction, and context overflow recovery all run through src/core/compactor.
- User messages stay exact, the newest compacted turns keep their final replies word for word, and the rest of the assistant's work is summarized.
- Every compacted turn and tool call is saved unchanged as M1, M2, ... and T1, T2, ..., and read_tool_result opens and searches them.
- The summary uses the conversation's model at its lowest reasoning level, with one retry on another model family through the gateway.
- Automatic compaction starts at auto_compact_percent of usable input (10 to 80, default 80); FX_AUTO_COMPACT_PERCENT overrides it.
- Checkpoints written by the previous compactor still load.
- CI checks that code outside the compactor uses only its front door.
- A search reports a failed server's reason only when its server field
names that server. A free-text query that mentions a failed server gets
its normal results again.
- Word the result as the server being unavailable, since the recorded
reason is not always a startup failure.
- A deferred fx ask connection passes access and authority errors through
instead of reporting the recorded failure.
- Report the recorded startup failure when a tool search names a server
that failed to start, including a deferred server in fx ask, instead of
an empty result or a bare error name.
- Report it when a tool call finds its server stopped and the relaunch
fails, instead of the bare error name.
- Skip a partial character at the start of the retained stderr tail with the shared boundary helper, and test tails that begin inside a character.
- Fail the stdio diagnostics tests instead of crashing when a child has not been reaped.
- Rewrap the startup failure paragraph in CONTRIBUTING.md.
- Fix the startup budget through the connection control, from the same instant as the deadline.
- Test that a write to a child that closed stdin fails as a closed connection with its exit details.
- Test that a stalled discovery probe followed by an exiting legacy launch keeps its startup restart.
- Document the detailed startup failure for servers that close their connection before answering `initialize`.
- Report the smaller of the time left and `startup_timeout_ms` as the startup budget, and name `startup_timeout_ms` only when that setting set it.
- Keep the startup restart when a discovery probe timed out before a legacy launch closed its connection.
- Report a child that closed stdin before fx's request landed as a closed connection, with its exit and stderr.
- Pipe stderr in the stdio dispatcher's shell test helper, as the server launcher does.
- Document that startup exits are reported in detail when they happen before `initialize` is answered.
- Restart a stdio server once when fx stopped it for invalid output during startup, and skip the restart only when the server closed its connection at every offered protocol version.
- Report the startup budget the connection had, including after a crash-recovery relaunch.
- Keep the specific startup failure after crash-recovery relaunches and deferred `fx ask` connections instead of replacing it with an error name.
- Fail pending requests as soon as a stdio server's connection ends.
- Escape control, bidi, and zero-width characters in server stderr with the shared terminal-safe encoder.
- Build the WASM surfaces, where process signals carry no number.
- When a stdio MCP server exits during startup, report its exit code or signal and an excerpt of its stderr instead of `McpInitFailed`.
- Limit that excerpt to one line of at most 400 bytes covering the first and latest output, with secrets masked and terminal escape sequences removed.
- When startup times out, name the budget and the `startup_timeout_ms` key, plus the exit of an earlier launch if there was one.
- Stop restarting a stdio server during startup once it has exited at every offered protocol version or its startup deadline has passed.
- Show that same reason when a required MCP server fails to start and fx refuses to continue.