Skip to main content

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:

AgentCommandInstalls into
Claude Codeuvx jes claude-settings~/.claude/settings.json
Codexuvx jes codex-settings~/.codex/hooks.json
Hermesuvx jes hermes-settings~/.hermes/config.yaml
OpenCodeuvx jes opencode-settings~/.config/opencode/plugins/
OpenClawuvx jes openclaw-settingsa linked local plugin
Piuvx 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:

StageWhenIf jes blocks
inputThe user's promptThe agent gets the refusal instead of the prompt, where the host allows it
tool_callBefore a tool runsThe tool does not run
tool_resultAfter a tool runs, before the model reads the resultThe model reads the refusal, where the host allows it
outputThe replyThe 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​

CommandWhat it does
jes loginPrompts for a TypeSafe API key. Writes ~/.config/jes/.env, and config.json if it doesn't exist.
jes claude-settingsPrints the Claude Code hooks JSON.
jes codex-settingsPrints the Codex hooks JSON.
jes hermes-settingsPrints the Hermes hooks YAML.
jes opencode-settingsPrints the OpenCode plugin.
jes openclaw-settingsPrints the OpenClaw plugin.
jes pi-settingsPrints the Pi extension.
jes runner-settingsPrints jes-runner.ts, used by the three TypeScript plugins.
jes claude-hookThe Claude Code hook. Reads the event on stdin.
jes codex-hookThe Codex hook.
jes hermes-hookThe Hermes hook.
jes hookThe 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:

  1. The process environment. A variable set there, even to a blank value, is kept.
  2. ~/.config/jes/.env, which jes login writes. 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".