Let the middleware see what your agent is about to do, and block, note or continue it, without touching the agent's model connection.
What a hook is
A hook is a command Claude Code or Codex runs at a fixed point in its work, such as before each tool call. Condensate's hook shim is that command. It reads one event from the agent, asks the middleware what to do, and applies the answer the way the agent allows. MCP supplies tool access; hooks connect the agent's events to the middleware's policy. The middleware explains how the two connections fit together.
Why you would want it
- An agent is about to run a command you would never allow. The hook asks the middleware first, and the middleware can block it before it runs.
- An agent keeps editing the same file back and forth. The middleware sees every tool result and can add a note to the agent's next step.
- An agent stops with its task half done. The middleware can send it back to work, with a note that says why.
- None of this touches the agent's own connection to its model. The plugin carries no model traffic.
How it works
Follow the bottom row. The agent reaches a fixed point, such as the moment before a tool call, and runs the hook command with the event on its standard input. The shim sends that event to the middleware with your key. The middleware's policy answers in one of three ways: let it go on, block it, or add a message. The shim converts that answer into what the agent understands: a block where the agent allows one, a rendered message where it does not. The policy path fails closed: if the middleware cannot be reached in time, a prompt or a tool call is blocked rather than let through. The top row is the tool connection, which Connect an upstream covers.
Try it
The simplest way to install the shim is the Condensate plugin, which Hook up Claude Code and Codex walks through. To wire it by hand instead, set two environment variables where the agent can see them:
export CONDENSATE_KEY="$CONDENSATE_LOCAL_API_KEY" # the same key the backend checks
export CONDENSATE_URL=http://127.0.0.1:4787 # optional, this is the default
Then register the shim as a command hook for UserPromptSubmit, PreToolUse, PostToolUse and Stop. Use the absolute path to apps/middleware/hook-shim/condensate-hook.ts, and run it with bun. You should see the next prompt or tool call in that agent pass through the middleware. A blocked call comes back with a reason.
What happens when
The middleware answers
The middleware returns one of three answers for a hook event:
- Allow. The event continues with no output.
- Deny. The event is blocked where the agent allows it. For
PreToolUse, the shim returns adenydecision with a reason. ForUserPromptSubmit, the reason goes to stderr and the prompt is blocked. - Add a message. The middleware asks the shim to add a short message. The shim renders the message from its own template library, so the backend never sends free-form text. The message also adds one system message line for the person: a plain sentence that says what the agent was asked to do. A backend that sends no sentence shows the record label instead: counts, the intervention level and a record id.
The shim cannot get an answer
A fault means the shim has no key, cannot reach the middleware, gets a non-2xx status, gets a body it cannot parse, or gets an answer that fails validation. The fault rule depends on the event:
| Event | Can policy deny? | On a fault |
|---|---|---|
UserPromptSubmit | Yes. The prompt is blocked. | Denied, with a reason that names the fault. |
PreToolUse | Yes. The tool call is blocked. | Denied, with a reason that names the fault. |
PostToolUse | No. The tool already ran. | Continues, with one warning on stderr. |
Stop | No. It can only add a continuation. | Continues, with one warning on stderr. |
The policy path fails closed. The shim makes at most two attempts, each with a 400 millisecond timeout, so one hook call spends at most 800 milliseconds waiting on the middleware.
Other errors fail open. A malformed event, an answer the shim cannot apply to that event, or an unexpected shim error continues with one warning on stderr. CONDENSATE_HOOK_FAIL_OPEN=1 makes every fault a continue. Use it for local development only, because it removes the policy check.
The event comes from Codex
The shim works with Claude Code and Codex. It is a single command that reads one event from stdin. The shim sends the key as x-api-key. The middleware picks the event format from a request header the shim does not send, so its events use the default format, claude-code. This holds under Codex too, because the plugin runs the same shim there. The middleware's Reference names that header and its other value.
The agent is one of Condensate's own
An agent on the condensate engine sends the same four events from inside Condensate, with no shim. The Engines page, linked from the middleware overview, says what the middleware can do with each of them there, and how the fault rule differs.
Install the Condensate plugin
The Condensate plugin in packages/condensate-plugin packages the hook shim, a connection to the middleware MCP endpoint (including the code-mode tools) and a set of agent skills. One plugin serves both Claude Code and Codex. Both hosts run the same bundled hook script. No model traffic passes through the plugin.
Hook up Claude Code and Codex has the install steps for both hosts, a check that proves the connection works, and fixes for common faults.
The two hosts reach MCP in different ways. Claude Code connects straight to ${CONDENSATE_URL}/mcp over HTTP and sends the key as x-api-key. Codex starts a small stdio bridge (mcp-bridge.ts) that forwards each MCP request to that same endpoint with the same header. Codex passes CONDENSATE_KEY and CONDENSATE_URL from its own environment to the bridge.
The plugin registers UserPromptSubmit, PreToolUse, PostToolUse and Stop. It also sends SessionStart, SessionEnd, PreCompact, PostCompact, SubagentStart, SubagentStop and Notification when the host supports them. The middleware treats those extra events as observations. The fault rules above apply the same way under both hosts.
Reference
| Setting | What it does |
|---|---|
CONDENSATE_KEY | The key the shim sends, as x-api-key. Use the same key the backend checks. |
CONDENSATE_URL | Where the shim sends events. Default http://127.0.0.1:4787. |
CONDENSATE_HOOK_FAIL_OPEN=1 | Makes every fault a continue. Local development only. |
| Limit | Value | Why |
|---|---|---|
| Attempts per hook call | 2 | One retry covers a dropped connection |
| Timeout per attempt | 400 ms | So one hook call waits at most 800 ms on the middleware |
Read next
- Guidance-note profiles, for which notes the middleware adds through these hooks, and what an agent must show before it may stop.
- How far the middleware may go, for the consent tiers that cap the answers above.
- Hook up Claude Code and Codex, for the plugin install, a connection check and common faults.