Agent setup
A coding agent reads web pages, files, and tool output that anyone could have written, and it can run shell commands. jes sits in the agent's hooks and checks the prompt, each tool call before it runs, each tool result before the model reads it, and the reply.
You don't write Python for this. The hooks run uvx jes@<version>, pinned to
the release that printed them. uv downloads that release on first use and
caches it after that. After you upgrade jes, print your hook settings again so
they pin the new release.
1. Install uv
You need Python 3.11 or newer and uv. uvx
ships with uv. It must be on PATH when the agent runs its hooks.
2. Log in
uvx jes login
jes login asks for your TypeSafe API key and saves it to
~/.config/jes/.env. The file is created with mode 0600, and a directory
jes creates for it gets 0700. A directory that already exists keeps its
permissions. The
first login also writes ~/.config/jes/config.json, the list of
guards the hooks run. A later login replaces the key and leaves
config.json alone.
If XDG_CONFIG_HOME is set, jes uses it in place of ~/.config.
jes login doesn't install any hooks. That's the next step.
3. Install the hook for your agent
Each agent has its own page with the snippet and where to put it:
| Agent | Command | Installs into |
|---|---|---|
| Claude Code | uvx jes claude-settings | ~/.claude/settings.json |
| Codex | uvx jes codex-settings | ~/.codex/hooks.json |
| Hermes | uvx jes hermes-settings | ~/.hermes/config.yaml |
| OpenCode | uvx jes opencode-settings | ~/.config/opencode/plugins/ |
| OpenClaw | uvx jes openclaw-settings | a linked local plugin |
| Pi | uvx jes pi-settings | ~/.pi/agent/extensions/jes/ |
OpenCode, OpenClaw, and Pi plugins also need jes-runner.ts saved next to
them. uvx jes runner-settings prints it.
What the hooks enforce
A hook check maps to one of four stages:
| Stage | When | If jes blocks |
|---|---|---|
input | The user's prompt | The agent gets the refusal instead of the prompt, where the host allows it |
tool_call | Before a tool runs | The tool does not run |
tool_result | After a tool runs, before the model reads the result | The model reads the refusal, where the host allows it |
output | The reply | The reply is replaced, where the host allows it |
A blocked tool call is the strongest guarantee: the tool never runs. A tool result is checked after the tool has already run, so a shell command or file write is not undone. Each agent page lists what that host can and can't enforce.
The tool_call and output checks judge against the user's request. The hook
saves the last allowed prompt for each session under
~/.config/jes/sessions. The Claude Code hook also keeps the parts of each
streamed reply there, per message, until its final part arrives, then checks
the whole reply and deletes the parts. Two replies streaming at once don't mix.
Parts of a reply that never finished are removed after 24 hours. If a tool
call or reply arrives with no prompt on record, jes blocks it.
When a check fails
A check that errors, for example because the model can't be reached, is a block at
every stage. For input and tool_call the hook also exits 2. The other
stages exit 0 with the refusal in the output, because the host only applies
a replacement it can read.
A missing or invalid config prints the reason on stderr. The claude-hook,
codex-hook, and hermes-hook commands block the same way. jes hook exits
2 with nothing on stdout at every stage. Run uvx jes login if it says the config is missing.
Hooks use the decision model named by model in
config.json, jev-latest by default. Set it to pin a Jev
release, such as jev-1.13.0, or to use another model on the TypeSafe API.
Commands
| Command | What it does |
|---|---|
jes login | Prompts for a TypeSafe API key. Writes ~/.config/jes/.env, and config.json if it doesn't exist. |
jes claude-settings | Prints the Claude Code hooks JSON. |
jes codex-settings | Prints the Codex hooks JSON. |
jes hermes-settings | Prints the Hermes hooks YAML. |
jes opencode-settings | Prints the OpenCode plugin. |
jes openclaw-settings | Prints the OpenClaw plugin. |
jes pi-settings | Prints the Pi extension. |
jes runner-settings | Prints jes-runner.ts, used by the three TypeScript plugins. |
jes claude-hook | The Claude Code hook. Reads the event on stdin. |
jes codex-hook | The Codex hook. |
jes hermes-hook | The Hermes hook. |
jes hook | The host-neutral hook. See Hook protocol. |
Every *-hook command and jes hook take --session-dir DIR to store
session prompts somewhere other than ~/.config/jes/sessions.
python -m jes works the same as jes.
Where text goes
Transforms such as secrets and pii run inside the hook process. Judgments
such as injection send the text, after those transforms, to the decision
model at TypeSafe (Jev by default).
See Where checked text goes.
The bundled hooks and plugins only replace text when a check blocks. When a
check allows text after redacting it, the agent still sees the original, so
secrets and pii redaction only protects what is sent to the model. To redact what
the agent sees, configure those guards to block.
Where the key comes from
The hooks read TYPESAFE_API_KEY, and any other variable, from two places.
The first value wins:
- The process environment. A variable set there, even to a blank value, is kept.
~/.config/jes/.env, whichjes loginwrites. Blank values in the file are skipped.
The hooks never read .env or .env.local from the project the agent is
working in. Otherwise a repository could set TYPESAFE_BASE_URL to its own
server and receive your API key, your prompts, and your tool calls, and answer
every check with "allow".