pal.

A system to control your coding agent's scripting.
More autonomy, fewer prompts, more security.

A coding agent writes and runs code on your machine, with your credentials. Mostly it does the job. Occasionally it does more: an API key written somewhere it doesn't belong, a commit nobody asked for, a destructive call on a token that happened to have root.

pal is a small scripting language your agent uses in place of throwaway bash and Python — and every file it writes and every host it reaches is checked, as the script runs, against a policy file that you own and the agent cannot edit.

Whatever the agent meant to do, only what your policy admits actually happens.

github.com/brownsys/palsgraf

Installation

$ curl -fsSL https://example.invalid/install.sh | sh
Supported Platforms: Linux (x86_64, aarch64) · macOS (aarch64) · manual install

In each project where you want your agent to use pal: pal init, to interactively integrate pal with the agent harness (supported are Claude Code, opencode and pi). You can choose to offer pal as an option, or — if you are eager — to force the agent to use it instead of its other tools.

Help us improve pal — install it, use it from your agent, and run pal telemetry collect to share where the language was insufficient. We rely on your feedback to complete the language.

What it can do

With pal you can deploy machine-global policies, and you can also use the built-in policy authoring tool together with your agent to write task-specific ones — so you can step away from the churning agent with confidence. Some examples of what is possible:

File permissions

global
Allow it access to your project (./), but disallow writing your .bashrc or reading your .ssh.
task-specific
For your “fix my merge conflicts” prompt, the agent proposes “edit access only to what's in git status” as a temporary grant.

Specific API verbs

global
Restrict the agent to read-only GitHub API access during planning.
task-specific
You prompted “review this PR”, and the agent proposes the permissions CREATE_REVIEW_COMMENT(owner=me, repo=my-repo), CREATE_PR_REVIEW(owner=me, repo=my-repo), …

Domain filtering

The classic coarse-grained option, for when the verb does not matter: allow github.com, deny reddit.com.

Limitations

Is one of these a blocker for you? Tell us — an issue, or the report_missing_capability MCP tool your agent already has.

How it works

No hidden effects

A script cannot touch a file, a host or a subprocess except by asking for an effect — fs.write, net.request, proc.spawn — and an effect is a value some handler decides on (algebraic effects, if you want the term). That is what makes an exhaustive check possible: there is no second path to the outside world to forget about.

The policy is yours

A .pal/policy.toml in your project says which paths a script may write and which hosts it may reach. The agent can propose a change and you approve or refuse it; it cannot edit the file. See writing policies.

Made for your agent

pal init registers pal as an MCP server, so your agent gets a run_pal 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

pal is a research prototype, and this is the stage at which your experience still changes it. The enforcement works; what is unsettled is whether a language narrow enough to check is still wide enough for the work agents actually want to do, and whether a policy is something a person can reasonably be asked to write.

We are currently investigating how usable pal 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

The language itself is deliberately unremarkable — Rust-shaped, aimed at the extraction and transformation work an agent would otherwise do in a throwaway script. What is different is that everything it can reach is enumerable, and therefore checkable.

$ pal -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. pal init leaves a sensible baseline in your project's .pal/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 pal 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 pal server exits; your policy file is not changed.

This dialogue is natural language, but generated by pal, not by the agent. You can trust its accuracy. Your approval and denial also goes directly to pal, 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.

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 pal 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; pal 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 pal 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 pal-linux-x86_64, pal-linux-aarch64, or pal-darwin-aarch64. Each has a sibling .sha256 file with its checksum.

$ ASSET=pal-linux-x86_64   # adjust for your platform
$ curl -fsSL -o pal        "https://pub-c19bb22cdb2d42909de545e416be1e90.r2.dev/latest/$ASSET"
$ curl -fsSL -o pal.sha256 "https://pub-c19bb22cdb2d42909de545e416be1e90.r2.dev/latest/$ASSET.sha256"
$ sha256sum -c pal.sha256  # or `shasum -a 256 -c` on macOS
$ chmod +x pal
$ mv pal ~/.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/pal-src.tar.gz | tar xz
$ cd pal-*-src
$ cargo build --release -p pal
$ cp target/release/pal ~/.local/bin/

Tell Claude Code about pal, without pal init

Either install path drops the binary on your PATH but doesn't tell your coding agent it exists. In a project, pal init is the way to fix that. If you'd rather mention pal 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_pal tool and no policy, so the agent decides for itself whether to use pal, 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 '## pal' ~/.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.