Algebraic effects
Effects like proc.spawn and net.fetch are values. Handlers decide how (or whether)
they execute — letting the host swap real I/O for sandboxes, fixtures, or audit logs without touching script
code.
Policy-gated
A .fxsh/policy.toml in your project says which paths a script may write and which hosts it may
reach. Every effect is checked against it as the script runs — a denied one fails with
policy denied rather than happening. See writing policies.
A tool your agent can call
fxsh init registers fxsh as an MCP server, so your agent gets a run_fxsh tool and
the language reference to go with it. One static binary, no runtime — the same one serves the tool and runs
scripts from your shell.
State
We are currently investigating how usable fxsh is by both the agents and users. We ask that you use the telemetry upload every so often to let us know if the agent struggled to use it. And we are also interested in your feedback as to the ergonomics of policy authoring.
Quick taste
$ fxsh -c 'fn main() {
let pat = re.compile(r"https?://\S+")
let matches = pat.scan(fs.read("notes.md"))
for m in matches { stdout.write(m.group(0)) }
}'
This is one of the shell forms. Your agent also has access to it through an MCP server. Both are bounded by the same policy.
Writing policies
A policy is a TOML file. fxsh init leaves a sensible baseline in
your project's .fxsh/policy.toml — writable paths, reachable
hosts — and every effect a script performs is checked against it as the
script runs.
The policy is yours to edit, but you can leverage your agent for more convenience. The agent can't change the policy, but it can suggest a change which you review and approve or deny. You would see a dialogue like this:
The agent is asking to change what fxsh scripts may do here.
Agent's reason: I need the closed issues for this milestone to write the changelog.
• allow network requests to api.github.com
Applies until this fxsh server exits; your policy file is not changed.
This dialogue is natural language, but generated by fxsh, not by the agent. You can trust its accuracy. Your approval and denial also goes directly to fxsh, all facilitated by trusted MCP protocol provisions.1 Agent's can request temporary allowances for the current session or you can ask them to permanently edit your policy.
1
Except under opencode, whose MCP client has no confirmation dialog yet: there an approved change is applied when
the agent asks, leaving you a record rather than a say. fxsh init tells you which you are getting.
Telemetry
Each script run emits one structured record to stderr (also mirrored to a local JSONL file) — outcome, error kind, source hash, model name.
Upload them with fxsh telemetry collect when you've got a few. You can review (and edit) the
exact data before it's sent. We use this information to make the language more useful;
fxsh telemetry collect --help spells out what gets shared.
Telemetry collection for sandboxed agents is currently supported only for Claude Code. More platforms coming (next planned is Copilot).
Manual install
The curl | sh line on the install card downloads the right prebuilt binary for your
platform, verifies its checksum, drops it into ~/.local/bin, and offers to register fxsh
with Claude Code. If you'd rather see what's happening before running it — or you're on a platform we
don't ship a binary for (Intel Mac, Windows, BSD) — here's the same thing, by hand.
Option A: prebuilt binary
Pick the asset matching your platform from
https://pub-c19bb22cdb2d42909de545e416be1e90.r2.dev/latest/ — currently
fxsh-linux-x86_64, fxsh-linux-aarch64, or
fxsh-darwin-aarch64. Each has a sibling .sha256 file with its checksum.
$ ASSET=fxsh-linux-x86_64 # adjust for your platform
$ curl -fsSL -o fxsh "https://pub-c19bb22cdb2d42909de545e416be1e90.r2.dev/latest/$ASSET"
$ curl -fsSL -o fxsh.sha256 "https://pub-c19bb22cdb2d42909de545e416be1e90.r2.dev/latest/$ASSET.sha256"
$ sha256sum -c fxsh.sha256 # or `shasum -a 256 -c` on macOS
$ chmod +x fxsh
$ mv fxsh ~/.local/bin/
Option B: build from source
If you'd rather build, the same source tarball our release pipeline uses lives next to the binaries.
Needs a Rust toolchain (install via rustup.rs) but no submodule fetch
or workspace setup. Swap the path segment for v0.0.0 (or whichever) to pin a
different version; the tarball is named after the same segment.
$ curl -fsSL https://pub-c19bb22cdb2d42909de545e416be1e90.r2.dev/latest/fxsh-src.tar.gz | tar xz
$ cd fxsh-*-src
$ cargo build --release -p fxsh
$ cp target/release/fxsh ~/.local/bin/
Tell Claude Code about fxsh, without fxsh init
Either install path drops the binary on your PATH but doesn't tell your coding agent it exists. In a project,
fxsh init is the way to fix that. If you'd rather mention fxsh globally — or just try it before
setting up a project — append a short blurb to ~/.claude/CLAUDE.md instead. This gets you the
shell form only: no run_fxsh tool and no policy, so the agent decides for itself whether to use
fxsh, and its other tools remain unaffected. The guard skips the append if the snippet is already there, so
it's safe to re-run:
$ grep -qx '## fxsh' ~/.claude/CLAUDE.md 2>/dev/null \
|| curl -fsSL https://example.invalid/claude-snippet.md \
>> ~/.claude/CLAUDE.md
Or view the snippet and paste it into a CLAUDE.md of your choice.