- `/effort` opens a menu of the current model's reasoning effort levels with the one in use marked. ↑↓ moves, Enter applies the highlighted level, and Esc closes it.
- `/effort <level>` applies a level without the menu, and `/effort default` goes back to the model's default.
- Tab after `/effort` opens a level column under the prompt, as Tab after `/model` opens the model column. ↑↓ previews the level in the status line, typing filters the list, Tab fills in the highlighted level, and Enter applies it.
- A new level is saved like a level chosen in `/settings`, and fx prints `Reasoning effort set to <level> for <model>`.
- During a response, the line reads `Next turn will use reasoning effort <level> for <model>`. The running turn keeps its level.
- A model without levels, a level the model doesn't offer, or a malformed level prints a notice and leaves the effort unchanged. If the model list is still loading, the notice asks to try again.
- In a short terminal, the menu leaves the end of the conversation in view and scrolls.
- `/model` still asks for the effort after a model is chosen.
- `/help` lists `/effort` with the model commands.
Running /permissions with no arguments printed the mode, configured
rules, session grants, and saved rules to the transcript. It now opens a
read-only panel in the footer, like /status, and prints nothing. Each
configured rule, session grant, and saved rule is a bullet, and saved
rules show the ID that /permissions revoke takes. The panel updates when
an approval adds a grant. Subcommands such as /permissions auto and
/permissions remember still confirm in the transcript.
The /status panel becomes a shared info panel that both commands use.
Tests that ran /permissions only to put text in the transcript now run
/allowlist.
- /status opens a panel in the footer instead of writing a notice to the transcript. Esc or Enter closes it and leaves nothing behind.
- The panel's first line shows the open session's ID and whether it is saved yet, so it can be resumed with fx --resume <id>. A shell that can't save sessions shows none.
- The open panel updates when a running turn saves the session or finishes.
- When the panel doesn't fit, the arrow keys scroll it and the hint says so. Long values such as the workspace path wrap instead of being cut off.
- When profile settings can't be read, the panel shows a Settings line and leaves out auth, instead of printing an error with no status.
- A fork made before the first prompt now counts as saved: /status shows it as saved, and the exit line prints its resume command.
- `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.
The E2E files, TLA+ drivers and the long-turn memory benchmark that read
v1's session files now read the v2 log. Fixes for what those tests found:
- Resuming a subagent child directly fails with v1's message.
- fx session recover copies a v1 conversation whose permissions.json is
malformed or empty, without its rules, and says so.
- fx doctor counts the v1 sessions not converted yet and names each one it
cannot convert.
- Salvage leaves out a side file that is missing or whose size or digest
changed.
- A v1 log whose checkpoint splits a tool call from its result still
converts.
- --resume last and fx session last fail with NoReadableSessions when they
skipped an unreadable v1 session.
- A converted session keeps a last turn that never ended, marked
interrupted.
- fx session --id on a damaged v1 session names the file and the reason.
- Prompt images are also kept as blobs, so the oversized-image notice names
a file that still exists after a resume.
- A session reopened after fx was killed mid-retry shows the finished tool
calls and results, not the partial reply.
- An update relaunch from a session with nothing saved opens a fresh
session.
- Converting a schema-2 session captures images it saved only by path, as
v1's resume did.
- Recalling a prompt from history uses its saved image bytes, so a damaged
captured file no longer refuses the recall.
- fx -c says the remembered session could not be read when v1's bookmark is
unreadable.
- The /resume picker orders v2 and unconverted v1 sessions newest first,
like fx sessions.
- Wiring.tla models one store, without the backend switch or the usage
ledger.
The macOS arm64 candidate is 6.57 MiB after the size reductions in #1164.
Release eligibility also requires 0.25 MiB of headroom, so a 7.0 MiB
ceiling leaves about 190 KB for growth before a release needs trimming,
instead of the 1 MiB that 7.8 MiB allowed.
Zig 0.17 names generic instantiations __func_N instead of __anon_N and
renames allocBytesWithAlignment to allocBytesAligned, so the helper
patterns follow. The IR size tests default to the pinned LLVM 22 tools and
the production target triple, and the benchmark profile-use test expects
the profile preparation passes.
LLVM's bitcode writer wraps Darwin modules in the 0x0B17C0DE header, so the
outline-helpers output failed the raw-header check that guards Zig's
emitted bitcode. The check now accepts both valid magics, and the IR size
tests validate the real pass output with it.
The PGSO pipeline runs two LLVM passes through the pinned LLVM C library.
The first keeps small std helpers like Allocator.free, Writer.writeAll, and
byte list cleanup out of line before instrumentation, so Zig stops copying
them into thousands of call sites. The second replaces copies of mostly
undefined error-return constants with stores of their defined bytes. The
candidate link also passes -no_compact_unwind, because newer Apple linkers
otherwise add about 160 KB of unwind data to a binary built without unwind
tables.
Trace logging formats byte, integer, enum, and bool placeholders through one
shared writer with byte-identical output. The turn loop's three per-request
history walks are noinline, so their hot loops keep their own profile and
the rest of the loop can compile for size.
/usage and fx usage now show what the provider reports instead of
counting locally, and fx no longer keeps its own usage ledger.
- AI Gateway: with fx login, your spend on the team and your user
budget; with an API key, that key's spend. Today, the last 7 days and
the calendar month (UTC), with per-model tables and a details screen
for each model.
- Codex: the ChatGPT plan's limits, token activity, and usage by model
and surface, read from the Codex backend.
- Grok: the plan's allowance, usage by product, bought credits, and
pay-as-you-go spending, read from the Grok CLI proxy.
- /usage draws df-style tables, neutral in every theme and sized to
their content at any width. Opening it shows cached numbers at once
and refreshes in the background, then every 15 minutes while open and
on r, at most once a minute. Finished days are cached under
~/.fx/usage-cache and read at most once per UTC day.
- fx usage takes --period today, 7d or month, prints the same tables,
and --json includes every provider's view. The /cost alias, 24h and
30d are gone.
- src/usage/ holds all usage logic; the TUI and CLI only call its API.
- The session and profile usage ledgers, the usage sidecar file,
Gateway generation lookups, and provider billing outcomes are removed.
Saved sessions with old usage data still load and resume; old usage
fields and files are left in place and ignored.
- A completion's tool calls still never run after another process takes
over the session: each host confirms it still owns the session writer,
without writing, before tools run.
- ACP reports context use from each completion's token counts and no
longer sends a cost.
- An existing ~/.fx is made private when a session reopens, and a usage
fetch creates a missing ~/.fx with owner-only access.
- E2E tests drive /usage and fx usage against loopback fakes for every
provider.
The heavy ui-activity measurement runs the benchmark pair under
hyperfine, which discards the benchmarked program's stdout and stderr.
When the benchmark failed its own timing gate it exited 1 after printing
its ratios and `error: UiActivityProgressRegression`, but the job showed
only hyperfine's "non-zero exit code 1", so a timing breach could not be
told apart from a crash or a changed fixture.
Hyperfine now passes that output through with `--output=inherit`, the
form it accepts alongside `--style none`, and run_checked keeps it in each
round's log, which the job already uploads. A passing round adds one short
line per benchmark.
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.
MCP-v2 is opt-in behind FX_MCP_ENGINE=v2, so its commands should be verified by the PGSO candidate without shaping its hot and cold layout. The PGSO self-test lists now include the file and the new candidate count.
- FX_MCP_ENGINE=v2 runs MCP servers through MCP-v2, a new MCP client. Without it, fx keeps using the current engine.
- MCP-v2 speaks MCP 2026-07-28 and 2025-03-26 through 2025-11-25 over stdio and Streamable HTTP.
- MCP-v2 starts a server the first time something needs it, such as a matching tool search, instead of at launch.
- fx mcp list, show, add, remove, login, logout, approve, and reject manage servers. list and show accept --json.
- /mcp opens a menu of servers with their status, tools, and actions, and /mcp <command> runs the same commands in the shell.
- At startup, fx names project servers that are waiting for approval.
- fx mcp login signs in through the browser, or with --no-browser by pasting the address the browser ends on.
- fx mcp logout revokes the token when the server supports revocation.
- MCP-v2 keeps its logins apart from the current engine's, in the macOS Keychain when it's available and otherwise under ~/.fx/mcp-credentials/.
- Quitting fx sends each running stdio server SIGTERM and exits without waiting for it.
- For 2025 servers, a call's idle timeout pauses while the user answers the server's question. The overall limit still applies.
- Server instructions reach the model with that server's tools.
- A server that doesn't register fx as a client shows a hint to set oauth.client_id in mcp.json.
- Over ACP with MCP-v2, fx advertises stdio and HTTP servers only, and refuses "type": "acp" servers with a reason.
- Over ACP with MCP-v2, editor-supplied servers marked always-loaded keep their tools in every turn.
- Over ACP with MCP-v2, editors receive elicitation/complete for URL questions, including a completion that arrives before the user accepts the URL.
- Editors that advertise session.notices receive fx's notices as ACP Session Notices instead of assistant text, with either engine.
- Add an end-to-end test with a repository whose own config names fsmonitor, hooks, filters, diff drivers and a pager, and check that interactive startup, @-completion, glob_files, grep_files, auto-run git status and fx pr run none of them while still answering from the repository.
- Classify it as verification-only for macOS arm64 PGSO and give it a CI shard weight.
- Run glob_files and grep_files on the native indexer's listing, so a search starts no git process and sees files created a moment earlier, including untracked files in subfolders.
- Reuse the saved @-completion listing for a search of the workspace or one of its ordinary folders while it is current, and scan the search root otherwise.
- Search file contents on several threads, with the same results and the same cut at the match cap as a single-threaded search.
- Keep searching hidden folders inside a repository, and skip them elsewhere unless the search path or pattern names one.
- Reuse a listing cut at the entry cap while nothing it read has changed.
- Check in CI that @-completion, glob_files and grep_files never start a process.
- 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.
Unrolling constant-trip loops ahead of the default pipeline removed the
per-element counts, but the raw unrolled code also made the optimized
benchmarks slower. The ui-activity candidate ran about 19% slower than
its plain O2 control, and an unprofiled build with the same preparation
was 11% slower on its own. Running instcombine before and after the
unroll and simplifycfg after it brings that build within about 1% of
plain O2, while eqlBytes keeps its Zig 0.16 counter layout.
Zig 0.17 emits array bitcasts, such as the [4]u8 reads in std.mem.eql,
as per-element loops that the optimizer later folds into single loads.
Instrumentation runs before loop unrolling, so the training profile
counted every element. On the e2e-cli and e2e-tui-resize shard,
mem.eqlBytes rose from 1.5 billion counts to 22 billion while every
other function stayed level. That one function then held most of the
profile, so the cold cutoff marked nearly all other code minsize and a
supplemented function landed exactly on the cutoff.
Run sroa and loop-unroll-full ahead of the default pipeline when
generating and using profiles. Both sides see the same IR at the
instrumentation point, so CFG hashes still match, and eqlBytes gets the
same counter layout it had on Zig 0.16.
Zig 0.17 bundles the macOS 27 libSystem stub, which lists arm64e.x1
targets. The Xcode 16.4 linker on the macos-15 runners rejects that
architecture, so the temporal candidate link failed. Link against a copy
of the pinned stub with only those target entries removed. The arm64
link never resolves against them, and the evidence records the hashes
of both stubs.
Zig 0.17 can number anonymous symbols differently on another runner,
so the same commit emits bitcode that differs in a few symbol names.
The candidate job rebuilt the bitcode and required it to match the
seed byte for byte, which failed on every run.
Copy the seed's hash-verified bitcode instead, the same way the job
already reuses the seed's control binary and merged profile.
Zig 0.17's `zig build` no longer accepts --global-cache-dir, so the
binary size builds and the PGSO pipeline set ZIG_GLOBAL_CACHE_DIR for
the same directories.
The binary size check now builds the base commit with the Zig version
that commit pins in build.zig.zon. A pull request that changes the
toolchain could not build its base with the new compiler, and the
comparison now reports the size change across the toolchain bump.
The compactor's allocation-failure test now backs its sweep with
testing_allocator.no_resize. That file imports only std, so it belongs
with the shared basics the boundary check already allows.
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.
- Commands that use the user profile run the login shell's startup files
(zsh or bash) once per fx process. fx captures the resulting PATH,
environment, aliases, functions, and shell options, then restores them
for every later command. Subagents share the same capture.
- fx captures again on the next command after a watched startup file
changes, such as .zshrc, .zprofile, .bash_profile, or .bashrc.
- /shell reload makes the next command run the startup files again. The
model can do the same with reload: true on shell.run.
- A reload clears the session's remembered command approvals, and cleared
approvals no longer show in the approvals list or the status count.
- When a capture fails, exceeds 8 MiB, or takes longer than 10 seconds, fx
runs the startup files for every command and says so once in the
interactive shell, in fx ask, and in ACP.
- When restoring a capture fails inside a command, fx reruns that command
with full startup.
- In auto mode, a routine command that uses a captured alias or function
goes to the safety review, and routine commands go to the review while
captures are failing.
- The shell tool description tells the model that startup files apply,
that cd, export, and alias changes do not carry over between calls, and
how zsh treats unquoted globs and words that start with =.
- The terminal host daemon and the tmux backend are removed, along with
the FX_TERMINAL_HOST_* environment variables. tty terminals run inside
the fx process, end when fx exits, and show as ended on resume.
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.
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.
The compactor imported three helpers from src/core/session/session.zig.
Two of them, rawHistoryTurnCount and contextHistoryRange, only count and
slice saved turns over shared types, so they move unchanged into
src/core/shared/history_range.zig and every caller imports them from
there. The third, appendHistoryChatMessages, is the session's own
projection of saved turns into chat messages, so it stays in the session
and callers hand it to the compactor through compactor.Request, like the
model caller and the record store.
The compactor now imports only shared basics and model configuration, and
the boundary check no longer allows session.zig. Behavior is unchanged.
- Add --sessions-v2 to fx ask (before or after ask) and FX_SESSIONS_V2=1. With either, fx ask saves the session as an append-only log in ~/.fx/sessions/v2/<id>/.
- Resume those sessions with --resume last and --resume-id; they are only visible with the flag on. --no-save still writes nothing.
- Save each finished exchange while the turn runs. After a crash, the prompt and finished tool calls are kept, and the next resume marks that turn interrupted.
- Store tool output side files in ~/.fx/session-files/<id>/, owner-only like the log.
- On resume, cut a half-written last line. A damaged line elsewhere stops the resume with InvalidSessionFormat and leaves the file unchanged. A session another process holds fails with SessionBusy.
- Report a missing session with the current store's errors, NoSavedSessions and SessionNotFound.
- Recover profile usage from v2 sessions after a crash, as for current sessions.
- Leave subagents off in fx ask while the flag is on.
- Keep the current store as the default; it now ignores the v2 folder.
Summarize a compacted part too large for one request in several
requests, oldest first, each folding into the next. A single turn that
does not fit keeps the start and end of its long texts in the request;
its saved records keep them whole.
Name the saved whole output of a clipped tool result in its record and
in the summary request, so it stays reachable after compaction.
Keep the configured provider routing when the summary falls back to
another model, and tell the summarizer to treat tool output and quoted
text as information.
Restore image_baseline in the compaction decision trace, record a
compaction that finds nothing to compact, and drop duplicate helpers,
an unused result field, test-only window options and the second record
store interface.
- 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.