# Agent harness integration

> Save up to 65x on decision tokens and cut loop latency by wiring mouse-1 into Cursor, Claude Code, OpenCode, or your own agent harness.

Source: https://mouse.dev/docs/agents
Full LLM Docs: https://www.mouse.dev/llms-full.txt | OpenAPI: https://www.mouse.dev/openapi.json

Agent harnesses do not just generate code—they make dozens of narrow control-flow decisions on every turn: **which tool or skill to load**, **whether a shell command is safe to auto-approve**, **whether untrusted output contains a prompt injection**, and **whether test logs actually verify the claim**.

Calling a generative model (`Claude Haiku 4.5`, `Sonnet 5`, `GPT-6 Sol`) for those checks costs **$1.25 to $6.50 per 1,000 decisions** and adds **500–2,000 ms** of token decoding on every step. `mouse-1` scores up to 128 typed questions against your `state` in a single forward pass in **~40–100 ms** for **$0.10 / 1M input tokens** (**$0.02 / 1M cached**, **$0 output**).

## Where `mouse-1` saves money in an agent loop

| Harness decision point | What `mouse-1` replaces | How it saves money & latency |
| --- | --- | --- |
| **1. Tool & skill routing** ([guide](/docs/guides/tool-routing)) | Stuffing 30–100 MCP tool or skill definitions into every frontier model turn | Score all candidate tools with one `choice` question (`~$0.00001`), then load only the top 1–3 tools into your frontier model—cutting main-model input tokens by **40–70%**. |
| **2. Action gating & auto-approval** ([guide](/docs/guides/gating)) | Interrupting the human on every `Bash` / `Write` call, or paying a chat LLM to judge safety | Evaluate `safe` and `destructive` `noul` predicates in ~40 ms. Safe local commands run unblocked; destructive commands return `deny` with a reason so the agent re-plans immediately. |
| **3. Post-tool verification & stop check** ([guide](/docs/guides/verification)) | Blindly trusting `"done"` claims or burning 3–5 frontier turns repeating a broken fix | Score `{ goal, diff, test_log }` at turn end. Catching a contradicted claim or repeated failure early saves **$0.50–$2.00** in wasted frontier model retries. |
| **4. Prompt-injection guard** ([guide](/docs/guides/injection)) | Exposed control flow when reading untrusted GitHub issues, web pages, or logs | `mouse-1` treats `state` strictly as data (**0% decision flip rate** on planted instructions vs **65.4%** on TypeSafe Jev). |

---

## 1. Drop-in hooks for coding agents (zero prompt changes)

If you use **Cursor**, **Claude Code**, or **OpenCode**, you can wire `mouse-1` into the harness's native lifecycle hooks in under 60 lines. Your system prompt and main model stay untouched:

- **`allow`** → executes immediately without interrupting you.
- **`deny` + reason** → blocks the tool call and feeds the reason back to the coding agent so it re-plans on its own.
- **`ask`** → falls back to the native confirmation prompt when confidence is in the middle band.

### Cursor (`.cursor/hooks.json`)

Cursor supports project-level command hooks on `beforeShellExecution`, `preToolUse`, `postToolUse`, and `stop`.

Create `.cursor/hooks.json`:

```json
{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      {
        "command": ".cursor/hooks/mouse-gate.py",
        "failClosed": true
      }
    ]
  }
}
```

Create `.cursor/hooks/mouse-gate.py` and make it executable (`chmod +x .cursor/hooks/mouse-gate.py`):

```python
#!/usr/bin/env python3
"""Cursor beforeShellExecution gate powered by mouse-1."""
import json, os, sys, urllib.request

TAU_SAFE = float(os.environ.get("MOUSE_TAU_SAFE", "0.85"))
TAU_DESTRUCTIVE = float(os.environ.get("MOUSE_TAU_DESTRUCTIVE", "0.50"))


def decide(state: dict, questions: dict) -> dict | None:
    key = os.environ.get("MOUSE_API_KEY")
    if not key:
        return None
    req = urllib.request.Request(
        "https://api.mouse.dev/v1/decisions",
        data=json.dumps({"model": "mouse-1", "state": state, "questions": questions}).encode(),
        headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=2.5) as res:
            return json.loads(res.read())["answers"]
    except Exception:
        return None


ev = json.load(sys.stdin)
ans = decide(
    {"command": ev.get("command", ""), "cwd": ev.get("cwd")},
    {
        "safe": {
            "type": "noul",
            "instructions": "Is this shell command safe to run automatically without asking the user?",
        },
        "destructive": {
            "type": "noul",
            "instructions": "Does this command delete data, force-push, or exfiltrate secrets?",
        },
    },
)

if ans is None:
    out = {"permission": "ask", "user_message": "mouse-1 unreachable; review command."}
elif ans["destructive"]["noul"] >= TAU_DESTRUCTIVE:
    p = ans["destructive"]["noul"]
    out = {
        "permission": "deny",
        "user_message": f"Blocked destructive command (p={p:.2f}).",
        "agent_message": f"mouse-1 blocked this command as likely destructive (p={p:.2f}). Use a safer, non-destructive alternative.",
    }
elif ans["safe"]["noul"] >= TAU_SAFE:
    out = {"permission": "allow"}
else:
    p = ans["safe"]["noul"]
    out = {
        "permission": "ask",
        "user_message": f"mouse-1 confidence below auto-approve threshold (p={p:.2f}).",
    }

print(json.dumps(out))
```

### Claude Code (`.claude/settings.json`)

Claude Code fires `PreToolUse` before tool calls and reads `hookSpecificOutput.permissionDecision` (`"allow" | "deny" | "ask"`) plus `permissionDecisionReason` from `stdout`.

Add to `.claude/settings.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/mouse-gate.py"
          }
        ]
      }
    ]
  }
}
```

Save `.claude/hooks/mouse-gate.py` (`chmod +x .claude/hooks/mouse-gate.py`):

```python
#!/usr/bin/env python3
"""Claude Code PreToolUse gate powered by mouse-1."""
import json, os, sys, urllib.request

TAU_SAFE = float(os.environ.get("MOUSE_TAU_SAFE", "0.85"))
TAU_DESTRUCTIVE = float(os.environ.get("MOUSE_TAU_DESTRUCTIVE", "0.50"))


def decide(state: dict, questions: dict) -> dict | None:
    key = os.environ.get("MOUSE_API_KEY")
    if not key:
        return None
    req = urllib.request.Request(
        "https://api.mouse.dev/v1/decisions",
        data=json.dumps({"model": "mouse-1", "state": state, "questions": questions}).encode(),
        headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=2.5) as res:
            return json.loads(res.read())["answers"]
    except Exception:
        return None


ev = json.load(sys.stdin)
ans = decide(
    {"tool": ev.get("tool_name"), "input": ev.get("tool_input"), "cwd": ev.get("cwd")},
    {
        "safe": {
            "type": "noul",
            "instructions": "Can this tool call run automatically without asking the user?",
        },
        "destructive": {
            "type": "noul",
            "instructions": "Does this delete, overwrite, or exfiltrate sensitive data?",
        },
    },
)

if ans is None:
    decision, why = "ask", "mouse-1 unreachable; falling back to user prompt"
elif ans["destructive"]["noul"] >= TAU_DESTRUCTIVE:
    decision, why = (
        "deny",
        f"mouse-1: likely destructive (p={ans['destructive']['noul']:.2f}); re-plan with a safe command",
    )
elif ans["safe"]["noul"] >= TAU_SAFE:
    decision, why = "allow", f"mouse-1: auto-approved (p={ans['safe']['noul']:.2f})"
else:
    decision, why = "ask", f"mouse-1: requires confirmation (p={ans['safe']['noul']:.2f})"

print(
    json.dumps(
        {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": decision,
                "permissionDecisionReason": why,
            }
        }
    )
)
```

### OpenCode (`.opencode/plugins/mouse-gate.ts`)

OpenCode plugins hook `tool.execute.before` (throwing an error blocks the call and returns the message to the agent) and `tool.execute.after` (mutating `output.output` appends verification feedback):

```ts
// .opencode/plugins/mouse-gate.ts
const TAU_SAFE = Number(process.env.MOUSE_TAU_SAFE ?? 0.85);
const TAU_DESTRUCTIVE = Number(process.env.MOUSE_TAU_DESTRUCTIVE ?? 0.50);

async function decide(state: unknown, questions: Record<string, unknown>) {
  const key = process.env.MOUSE_API_KEY;
  if (!key) return null;
  try {
    const res = await fetch("https://api.mouse.dev/v1/decisions", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${key}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ model: "mouse-1", state, questions }),
      signal: AbortSignal.timeout(2500),
    });
    return res.ok ? ((await res.json()) as any).answers : null;
  } catch {
    return null;
  }
}

export const MouseGatePlugin = async () => ({
  "tool.execute.before": async (input: { tool: string }, output: { args?: unknown }) => {
    const ans = await decide(
      { tool: input.tool, args: output.args ?? {} },
      {
        safe: { type: "noul", instructions: "Can this tool call run without asking the user?" },
        destructive: {
          type: "noul",
          instructions: "Does this delete, overwrite, or exfiltrate data?",
        },
      },
    );
    if (!ans) return;
    if (ans.destructive.noul >= TAU_DESTRUCTIVE) {
      throw new Error(
        `mouse-1 blocked ${input.tool} (p=${ans.destructive.noul.toFixed(2)}). Re-plan with a safer command.`,
      );
    }
  },
  "tool.execute.after": async (
    input: { tool: string; args?: unknown },
    output: { output?: string },
  ) => {
    if (input.tool !== "bash") return;
    const ans = await decide(
      { tool: input.tool, args: input.args, output_tail: String(output.output ?? "").slice(-2000) },
      {
        failed: {
          type: "noul",
          instructions: "Did the command fail or contradict the expected outcome?",
        },
      },
    );
    if (ans && ans.failed.noul >= TAU_SAFE) {
      output.output = `${output.output ?? ""}\n<mouse-judge>Command output indicates failure (p=${ans.failed.noul.toFixed(2)}). Fix the root cause before repeating.</mouse-judge>`;
    }
  },
});
```

---

## 2. Native integration in your own agent harness

When you own the agent loop (TypeScript, Python, LangGraph, Vercel AI SDK, or OpenAI Agents SDK), evaluate every decision point for a step in **one batched request** against the same `state`:

```ts
const res = await fetch("https://api.mouse.dev/v1/decisions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MOUSE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "mouse-1",
    // Put shared rubrics/schemas first so repeated turns hit the $0.02/1M prefix cache
    state: {
      task: userPrompt,
      git_status: gitStatus,
      last_tool_output: lastToolOutput,
    },
    questions: {
      route_skill: {
        type: "choice",
        instructions: "Which skill should be loaded into context for `task`?",
        criteria: {
          database_migration: "Schema changes, Drizzle/SQL migrations, or index tuning",
          stripe_billing: "Webhooks, checkout sessions, or credit ledger updates",
          ui_verification: "Mobile or web component layout and visual checks",
          none: "No specialized skill needed",
        },
      },
      needs_repo_sandbox: {
        type: "noul",
        instructions: "Does answering `task` require booting a code sandbox to read or edit files?",
      },
      previous_step_verified: {
        type: "noul",
        instructions: "Does `last_tool_output` confirm the previous step succeeded without errors?",
      },
    },
  }),
  signal: AbortSignal.timeout(3000),
});
```

Because `state` tokens are billed **once per request** regardless of how many questions you attach (up to 128), fanning out routing, sandbox-boot gating, and verification in a single call costs `~$0.00001` and completes in one forward pass.

---

## 3. One-click prompt for Cursor, Claude Code, and Codex

Click **Copy for Agent** in the top docs bar (or copy below) and paste it into your coding agent to wire `mouse-1` into your repository automatically:

```text
Integrate the Mouse Decision API (mouse-1) into this codebase or agent harness to cut LLM decision costs and latency.

Full documentation & schemas:
- Full LLM docs: https://www.mouse.dev/llms-full.txt (also https://www.mouse.dev/llm.txt)
- Docs index: https://www.mouse.dev/llms.txt
- OpenAPI 3.1 spec: https://www.mouse.dev/openapi.json

1. Wire Contract
- Endpoint: POST https://api.mouse.dev/v1/decisions (alias POST /v1/systemone)
- Headers: Authorization: Bearer $MOUSE_API_KEY (sk_mouse_... from https://www.mouse.dev/console/keys), Content-Type: application/json
- Model: "mouse-1" (~40-100ms single forward pass, $0.10/1M input tokens, $0.02/1M cached input, $0 output, Zero Data Retention)
- Question types (up to 128 evaluated in parallel per request; state billed once):
  - choice: { "type": "choice", "instructions"?: string, "criteria": { "<opt>": "<desc>" | null } } -> { choice, confidence, probabilities, mouse }
  - noul: { "type": "noul", "instructions": "<yes/no question>" } -> { noul, probabilities: { false, true }, mouse }
  - score: { "type": "score", "instructions"?: string, "criteria": ["<level 0>", "<level 1>", ...] } -> { score, confidence, legend, probabilities, mouse }

2. Your Task
- Inspect this repository to find the highest-leverage agent decision points (replacing slow LLM classification/routing prompts, pruning MCP tools/skills before calling the main model, gating risky tool executions, or verifying tool outputs).
- Or, if configuring a coding agent harness, add a lifecycle hook adapter (.cursor/hooks.json beforeShellExecution/preToolUse, .claude/settings.json PreToolUse, or .opencode/plugins/mouse-gate.ts).
- Implement a zero-dependency client reading MOUSE_API_KEY with a 3s timeout and safe fallback when the key is unset, document MOUSE_API_KEY in .env.example, and add a verification test.
```

## 4. Machine-readable endpoints

| URL | Format | Purpose |
| --- | --- | --- |
| [`/llms.txt`](/llms.txt) | `text/plain` | Curated LLM index + quick-reference wire contract (`< 5k` tokens). |
| [`/llms-full.txt`](/llms-full.txt) | `text/plain` | Complete documentation concatenated into a single Markdown file. |
| [`/llm.txt`](/llm.txt) / [`/llm.text`](/llm.text) | `text/plain` | All-in-one agent setup prompt + hook adapters + full docs bundle. |
| [`/openapi.json`](/openapi.json) | `application/json` | OpenAPI 3.1 specification with full request/response schemas and examples. |
| `/docs/<path>.md` | `text/markdown` | Append `.md` to any docs URL (e.g. [`/docs/agents.md`](/docs/agents.md)) for raw Markdown. |
