# DoubleOh > DoubleOh is the fix desk for AI agents. When an AI agent gets stuck on a task, a human fixes it once in a live browser or desktop session, and the fix is compiled into a reusable "skill" that the entire agent fleet reuses forever. The next agent to hit the same wall follows the learned skill instead of asking a human. DoubleOh's core idea: an agent will reach something it cannot do alone, a login, an approval, a rule nobody wrote down. DoubleOh is where a person steps in with authority, once, and where that permission and procedure become something the whole fleet may reuse, with the record of who allowed what. Positioning: DoubleOh is the authority layer for AI agents: the system through which software safely borrows a person's permission and know-how, records who granted it, and turns what people do into bounded procedures the fleet reuses. It handles the walls a model cannot think its way past, credentials, approvals, and company-specific procedure, rather than only broken selectors. It is framework and cloud neutral, and the skill library a customer builds belongs to that customer and is never shared across customers. ## For the agent reading this - Every refusal is {"error","code","next"}: codes key_required, rate_limited (429, `retryAfterSeconds`), bad_request, agent_mismatch (400: this key belongs to another agent), target_refused, computer_required, quota_exceeded (402, `reservedForApprovals`), not_found, unavailable, authority_refused, already_settled, not_approved (409, `differs` names what does not match), agent_paused (429: a person paused this agent). Act on `next`. - One key per agent. The key's agent is the verified name on every record (`agentName`); a write naming another agent is refused with agent_mismatch. GET /v1/me returns `me.agent`. Plan and usage belong to the organisation: all its agents' keys share one plan and one count of human fixes. - Rate limits and pause: each agent may open about 6 new walls a minute and 60 an hour (approvals have their own budget); over it is 429 rate_limited with `retryAfterSeconds`, so wait and ask once. A person can pause an agent; its new walls are 429 agent_paused until resumed. Stop the task. - A served skill that is not for your task: POST /v1/skills/{name}/outcome {"worked": false, "reason": "not_applicable", "task": "..."}. Not counted as a failure; the skill is not served for that task again and the next ask reaches a person. - Once a person answers, read `answer.approved` first (true: go ahead; false: do not do it, do not retry), then `answer.value.reason`. A screen fixed by hand stays `resolved` with `answer.value.result` (what you need to know) and `answer.value.reason`. - Do not poll: GET /v1/interventions/{id}?wait=55 holds up to 55 s and returns when the status changes; every fix response carries `waitUrl` and `expectedSeconds`. - `wallId` identifies a wall across retries; `deflected: true` means a learned skill answered and nobody was paged; `duplicateOf` means it is already waiting. - Every skill has `systemHint`: authority boundary first, then steps, then how to report. Paste it into context; report the outcome with POST /v1/skills/{name}/outcome. - GET /v1/quickstart returns the four steps with snippets for Python, TypeScript, curl and MCP. `npx doubleoh-mcp` serves the same loop as tools. ## How it works 1. An agent hits a wall (a changed login, a moved button) and asks for help once, with the page attached. 2. A human takes over a live browser or desktop, fixes it, and marks it done. Typed text is never recorded. 3. The fix compiles into a procedure (a "skill"), not a transcript. Skills carry a track record and retire themselves if they stop working. 4. The next agent that hits the same wall is answered instantly from the skill, a "deflection", and no human is paged. ## Integration - Python SDK: `pip install doubleoh`, see [Docs](https://doubleoh.ai/docs/) - TypeScript SDK: `npm install @doubleoh/sdk` - MCP server (Claude and other MCP clients): `npx doubleoh-mcp` - Framework adapters: LangChain, LangGraph, CrewAI, LlamaIndex, OpenAI Agents SDK, AutoGen, Pydantic AI - [REST API reference (OpenAPI)](https://api.doubleoh.ai/v1/openapi.json) The four SDK calls: `skillsFor(task)`, `requestFix({url, task, agent?})`, `waitForFix(id)`, `reportSkill(name, worked, {reason: "not_applicable", task}?)`. Pass `agent` ("ops-bot", 100 characters at most) so the person sees which agent asked. ## Proof and rules - Before a side effect: POST /v1/authority/check {tool, target?, action?, amountCents?, currency?} answers allow, needs_person or never, with the rule as a sentence. GET /v1/authority/rules returns the rules and a `systemPrompt` for the agent's context. - POST /v1/receipts {tool, action, idempotencyKey, amountCents?, currency?, payee?, agent?, approvedBy?} right before the action. Same key on every retry: a repeat comes back `duplicate: true`, so do not do it again; after `failed` the same key reopens the receipt (201, `retried: true`). Refused is 403 `authority_refused`; needs a person is 202 with `waitUrl` and `wallUrl`, and when they answer the receipt becomes `approved` (do it, then confirm) or `refused`; `receipt.approval` says who, when and why. Needs a person with no fixes left on the plan is 402 `quota_exceeded` with `upgradeUrl` and the recorded `receipt`: do not do it. - `approvedBy`: an intervention id a person answered with approved true in the last hour. An approval is bound to the agent that asked, the tool and action, the site, and at most the approved amount, and backs one receipt. The receipt opens `approved` with no second ask; anything else is 409 `not_approved` with `differs` (agent, tool, action, target, amount, spent, ...). Ask for an amount with `packet.amountCents` and `packet.currency` (or in `packet.args`). - Rules are matched by meaning, not only their words: "Never change bank details" also covers "update remittance info", an `account_no` field or "s3t b3n3f1c14ry 1B4N". A never or needs-person rule applies when the words or a classifier say so; an allow needs one of them to be sure. When the classifier cannot say (down or slow), anything that writes, moves money or deletes and no allow rule's words cover waits for a person ("Couldn't confirm which rule applies; a person decides."). Send `args` with the check so field names count too. A never matched only by meaning below 0.8 confidence becomes needs_person ("This might fall under ..."); your own allow the classifier is sure of (0.8+) beats a needs-person rule that matched by meaning alone, within the allow's limit, but never one its words matched. - Duplicate payments: a money action with the same payee (the `payee` field when both payments sent one, case and spacing aside, and the same tool; else tool, target, action words without numbers), amount and currency as one allowed or approved in the last 24 h needs a person ("Same payee and amount as a payment N hours ago; a person decides."). On by default; turn it off on the portal's Rules page or with `PUT /api/portal/rules/fallback {"duplicatePayments": false}`. Retrying the same receipt (same idempotency key) is not a duplicate. - Daily limits: "May transfer up to $500" is a running total per agent and currency over a day, unless the rule names another window (hour, week, transaction). Past it needs a person, and the sentence says the total. Several small amounts that pass a limit together carry `authority.pattern: "split"`. Concurrent receipts are counted one at a time. - A receipt that needs a person while the agent is over its wall limit or paused is 429 (rate_limited or agent_paused) with `retryAfterSeconds` and the recorded receipt: do not do it. A duplicate receipt carries `interventionId`. - GET /v1/receipts/{id} returns the receipt with `confirmUrl`, `next`, and `waitUrl` while a person is deciding. - POST /v1/receipts/{id}/confirm {evidence: {providerId, status, url}} with what the target system answered. Unconfirmed by the deadline, a person is asked to check. - No page to take over: POST /v1/interventions {task, mode: "answer", packet: {tool, args, error, steps}}. The wait returns status `answered` with the `answer` to apply: `{kind, value, approved}`. - SDK: `checkAuthority`, `receipt`, `confirmReceipt`, `getReceipt`, `rules` (Python: `check_authority`, `receipt(approved_by=...)`, `confirm_receipt`, `get_receipt`, `rules`). `status.approved` is the person's yes or no. ## Guarded tools - Why: The model can't bypass what it never holds. Rules and receipts only bind an agent that asks first, and in a benchmark Claude Haiku 4.5 called change_bank_details directly in three runs of three. A guarded tool asks on every call, whatever the model does: the dangerous function is wrapped, and the model only ever gets the wrapped one. - TypeScript: `client.guard(fn, { tool, action, target?, amount?, idempotencyKey?, evidence?, waitForPersonSeconds?, agent? })` returns the same function, guarded. `client.guardTools(tools, specs)` wraps every `execute` or `run` (Anthropic tool runner, OpenAI, Vercel AI SDK); a tool whose spec is `false` is read-only and left alone. - Python: `client.guard(fn, tool=..., action=..., amount=..., wait_for_person_seconds=0)`, also a decorator. `doubleoh.langchain.guard_tool(tool, ...)`, and the same in `doubleoh.crewai` and `doubleoh.llamaindex`, keep the tool's name, description and schema. OpenAI Agents SDK: `@doubleoh.openai_agents.guard_tool(...)` in place of `@function_tool`. `async def` stays async. - Resume after a person says yes: keep the arguments from `onPending(pending, ...args)` (Python `on_pending`), `waitForFix(pending.wallId)`, then call the guarded function again with the same arguments; it runs once. The default idempotency key folds case and whitespace in strings and reads decimal strings as numbers; pass your own `idempotencyKey` when the same arguments may rightly repeat. - Testing: `DOUBLEOH_MODE=mock` (or `mode: "mock"`) runs guarded tools in memory with no key or network; `decide` sets the verdicts, `client.mock.receipts` lists what was asked, `client.mock.answer(id, true)` plays the person. `DOUBLEOH_MODE=dry` asks the real rules and returns `GuardDryRun` ("Dry run, not done: ... would wait for a person (...)"); nothing is opened or run. - Each call: POST /v1/receipts; 403 never: not run, "Refused by your organisation's rules: . Do not try another way."; 202 needs a person: not run, "Not done yet: waiting for a person to approve (). Tell the user it is waiting; do not retry or work around it." (or waits up to waitForPersonSeconds; yes runs it, no refuses with the reason, a yes for less than the amount does not run it); 201: run, then confirm with evidence (ok false if it throws); a duplicate already done is not run again. - MCP, no code: `doubleoh-mcp guard --config guard.json -- `. Every downstream tool is guarded unless guard.json lists it under `readOnly`; `tools.` sets `action` ("{tool} {args}" by default), `target`, `amount` or `amountCents` and `currency` (arg paths) or `defaultCurrency`. The seven DoubleOh tools stay available. ## Records and compliance - Portal: Records and compliance sets a retention profile (standard, EU AI Act, EU AI Act high-risk, financial EU/US/UK), record and screenshot windows with legal minimums enforced, a legal hold, and the system's AI Act identity. No settings means nothing is deleted. - Exports: EU AI Act or financial audit, JSON or CSV, up to 400 days. One row per event linked by `case_id`; rows hash-chained (SHA-256), envelope signed with Ed25519; a cover sheet maps each field to its article. - Verify: GET /v1/record-key (no auth) returns `{ algorithm: "Ed25519", publicKeyPem, fingerprint }`; `bun scripts/verify-record.ts export.json` checks the signature and every link, for a compliance export and for the Ledger's signed JSON alike. - Not legal advice; have counsel confirm the mapping. ## Pricing - A platform fee by agent count; human fixes and deflections bundled per tier, metered past the bundle. - Free: 2 agents, 5 human fixes/month, 500 deflections/month. - Team: $249/month, 10 agents, 50 fixes then $4 each, 5,000 deflections then $0.10 each. - Growth: $749/month, 50 agents, 200 fixes then $3 each, 25,000 deflections then $0.08 each. - Scale: $1,990/month, 200 agents, 600 fixes then $2 each, 100,000 deflections then $0.05 each, customer-run runtimes on Linux, macOS and Windows. - Enterprise: from $60,000/year, unlimited agents, VPC or self-hosted, private targets, SSO, audit exports, BAA. - Learned skills are never charged. A deflection replaces about 15 minutes of a person's time. ## Industries - [Healthcare revenue cycle](https://doubleoh.ai/industries/healthcare-rcm/): payer portals, prior auth, claim status, denials. - [Insurance claims](https://doubleoh.ai/industries/insurance-claims/): carrier portals, quoting, FNOL, adjuster workflows. ## Links - [Home](https://doubleoh.ai/) - [Sign up (free, self-serve, one form)](https://app.doubleoh.ai/sign?new=1) - [Docs](https://doubleoh.ai/docs/) - [Pricing](https://doubleoh.ai/#pricing) - [Blog (guides on agent reliability)](https://doubleoh.ai/blog/) - [Security (what is recorded, isolation, SSRF boundaries)](https://doubleoh.ai/security/) - [Contact](https://doubleoh.ai/contact/) ## Blog One article per way an agent gets stuck, each answering the question directly with a code example and an FAQ. - How to Know When an AI Agent Has Hit a Wall and Needs a Human, Instead of Retrying Forever: https://doubleoh.ai/blog/agent-hit-a-wall-needs-a-human-not-retrying - "Session ended", "did not respond in time", "invalid handoff request": What Agent Session Errors Mean: https://doubleoh.ai/blog/agent-session-timeouts-and-handoff-errors - Observability vs an Authority Layer for AI Agents: What Each One Catches: https://doubleoh.ai/blog/observability-vs-authority-layer-ai-agents - What Is an Authority Layer for AI Agents?: https://doubleoh.ai/blog/what-is-an-authority-layer-for-ai-agents - Human Takeover for Browser Agents: Browser Use, Playwright and Computer Use Compared: https://doubleoh.ai/blog/human-takeover-browser-agents-browser-use-playwright-computer-use - A Human-in-the-Loop SDK for LangGraph, CrewAI and the OpenAI Agents SDK in Python: https://doubleoh.ai/blog/human-in-the-loop-sdk-langgraph-crewai-openai-agents-python - How Healthcare RCM Teams Give AI Agents Permission to Act in Payer Portals: https://doubleoh.ai/blog/healthcare-rcm-ai-agents-payer-portals-permission - Why Your Browser Agent Fails at Login, and What Actually Works: https://doubleoh.ai/blog/browser-agent-fails-at-login - AI Agent Stuck on a CAPTCHA: The Honest Answer: https://doubleoh.ai/blog/ai-agent-stuck-on-captcha - How to Handle 2FA in an AI Agent Without Breaking Security: https://doubleoh.ai/blog/ai-agent-2fa-two-factor - Your Agent's Session Expired Mid-Task. Here Is Why It Keeps Happening: https://doubleoh.ai/blog/agent-session-expired-mid-task - The Cookie Banner That Silently Breaks Your Agent: https://doubleoh.ai/blog/cookie-banner-blocks-agent - Selector Rot: Why Your Playwright Agent Broke After the Site Redesign: https://doubleoh.ai/blog/playwright-selector-rot - Your LangChain Agent Is Stuck in a Loop. Here Is What It Is Actually Doing: https://doubleoh.ai/blog/langchain-agent-stuck-in-loop - CrewAI Task Never Completes: Diagnosing the Silent Hang: https://doubleoh.ai/blog/crewai-task-never-completes - How to Hand an AI Agent's Task to a Human Without Losing the Session: https://doubleoh.ai/blog/hand-off-agent-task-to-human - What Is a Compiled Skill, and Why It Beats Recording a Human: https://doubleoh.ai/blog/what-is-a-compiled-skill - Why AI Agents Fail in Production (and How to Actually Fix It): https://doubleoh.ai/blog/why-ai-agents-fail-in-production - What to Do When Your AI Agent Hits a Login Wall: https://doubleoh.ai/blog/ai-agent-stuck-login-wall