Harness Security Matrix — Intutic Compliance Scope Open-Core
Coverage: 43 harnesses + 1 server-side platform integration (Anthropic Managed Agents, which has no
HarnessType) — 44 rows.
This document is the canonical reference for what Intutic enforces, how, and the gaps that remain per harness.
Defense Vectors
| Vector | Mechanism | How it blocks | Scope |
|---|---|---|---|
| A — Client Hook | Pre-tool-use gate script; blocking contract varies by harness (exit code 2, {"cancel":true} on stdout, {"decision":"deny"} on stdout, a JS throw, a returned {block: true} from an in-process hook (Pi, OpenClaw), a Python raise, or a Cordis waterfall {kind:'deny'} return) | Blocks before tool executes | The harnesses that write a generated on-disk hook file (as opposed to an SDK-side gate living in the developer's own process) — see the Hook (A) column of the Coverage Matrix below for the current, authoritative list rather than a count repeated here: currently Claude Code, Cursor, Windsurf, Cline, OpenClaw, OpenHands, Goose, Antigravity (Google Antigravity and Gemini CLI), Hermes, Pi, Codex CLI, Muse Code, Grok Build, GitHub Copilot (agent hooks, Preview), OpenCode (in-process plugin), n8n (workflow-level, manual install), and Open-WebUI (prompt-level filter). All of these enforce WHERE (argPattern) rules against the serialized tool input. Everything else with a Hook (A) ✅/⚠️ mark — LangGraph, dsh, the other SDK-gated frameworks (LangChain, CrewAI, AutoGen, AG2, Google ADK, OpenAI Agents SDK, Pydantic AI, smolagents, Strands Agents, Microsoft Agent Framework, Mastra, Vercel AI SDK, eve, AI SDK Harness, AI SDK Workflow), Anthropic Managed Agents, and TrueForge (embedded-library mode only) — gates in-process via intutic-clawde (Python) or @intutic/gate (TypeScript), not a generated file; dsh's plugin enforces the same argPattern rules via @intutic/gate's own Tier A1/A3. TrueForge's own veto surface is an async batch-approval responder rather than a synchronous per-call callback — see row 41. TrueForge Server (row 42) carries a DIFFERENT, weaker Hook (A) mark again — its gate runs in a wholly SEPARATE process (the TrueForge bridge) reacting to a pull-only event stream, not co-resident with or embedded in the harness's own process the way every other Hook (A) mark on this page is; see row 42's own note. Xirp and Agentic Orchestrator carry no gate of their own — they delegate entirely to whichever harness they wrap (GateKind: 'delegated'); for Agentic Orchestrator specifically, that delegation is only complete for its Claude Code/Codex backends — its third backend, OpenCode, is gated by its own plugin (row 43) |
| B — Proxy Gate | LLM request inspection at the API boundary | Blocks / audits before LLM sees the prompt | The 21 harnesses marked ✅ in the Proxy (B) column (Windsurf through TLS interception); see matrix. Cline, Roo Code, Cursor, Hermes, OpenClaw and OpenCode are routed by a setting you make in the harness yourself; Antigravity's Gemini traffic is not served by the proxy. Muse Code is env-var/launcher-only (no persistent base-URL setting confirmed), dsh redirects its default route (llm-deepseek), Xirp/Agentic Orchestrator have no LLM egress of their own, and eve/AI SDK Workflow/Strands Agents (default Bedrock provider)/Microsoft Agent Framework (Azure OpenAI/Foundry clients)/Vercel AI SDK/TrueForge/TrueForge Server are each proxy-routable only in-code or only for a subset of providers — none of these is among the 21 |
| C — Drift Guard | File watcher + 30s poll cycle | Detects and restores tampered governance configs | With a control plane: the governance files of every recorded harness, in the checkout the sync daemon runs in — see Worktree Coverage for other worktrees |
| D — Response Gate | Proxy-side inspection of the LLM response before it is forwarded to the client | Withholds a model-emitted tool_calls[] naming a denied tool, or matching an enforcing hook-rule policy guardrail, before the client's tool runner sees it | Every harness whose LLM traffic traverses the proxy (Vector B scope); harness-agnostic, no client hook required |
Vector A — review holds
Every Vector A gate evaluates the same policy snapshot, and since gate body v8 that includes the hold tier: a review_before: token, a closed reversibility lane (closed_lanes:, held as sop.lane.<class>.<token>), a revert hold (revert.<token>) or a REQUIRE_APPROVAL: SOP refuses the call through the harness's own blocking contract, records a hold for Findings › Review Queue › Held Changes, and lets the exact same call through once a reviewer approves it on a control plane while the workspace's review-hold bypass is on (short TTL); otherwise the approval records the decision. Without a control plane no one can approve a hold, so the call stays refused. The n8n workflow hook and the Open WebUI prompt filter refuse a hold outright — neither runs in a workspace that could record one. The in-process SDK gates (@intutic/gate, intutic_clawde.gate) hold the call the same way, recording it through the decisions API themselves; see Holds.
Since gate body v23 the tiers run in one order at every gate: refusals first (the built-in rules, the snapshot's block rules, a qualified requires_before, the MCP registry and allowlist), then holds, then scope_paths and plan_approval, then decide rules. A held call is held whatever a decide rule would say; only an approved bypass releases it.
Refusal codes in a JSON decision
Cline, Grok Build and Google Antigravity read a refusal from the hook's stdout rather than its exit code. The refusal carries a stable code, the deciding rule as ruleId (null when no rule decided), and for a hold the holdId:
{"decision": "deny", "reason": "… [mcp_registry.pastebin]", "code": "SERVER_BLOCKED", "ruleId": "mcp_registry.pastebin"}Cline's refusal is {"cancel": true, "errorMessage": "…", "code": …, "ruleId": …}. Google Antigravity's decision holds only the fields its hook contract documents, so its reason ends with the code instead, (refusal code SERVER_BLOCKED). The harnesses that read the exit code get the refusal as text on stderr, with the rule id in brackets.
A refusal by a requires_before … [ok], scope_paths or plan_approval rule also says where to retry: the JSON decision carries scope beside code and ruleId ({"paths": [...]} for OUT_OF_SCOPE, {"tools": [...], "step": ...} for PREREQUISITE_UNMET, {"tools": [...]} for a plan refusal; each list at most 8 items, each string at most 256 characters), and an exit-code harness gets one more stderr line, [Intutic] Retry scope: <paths, else tools>. An OPAQUE workspace gets neither, as it gets no rule id.
| Code | Meaning |
|---|---|
BUILT_IN_RULE | A rule built into the hook gate matched: a governance bypass, a write to a protected path or a skill directory, or a secret in written content |
SNAPSHOT | A block rule in the policy snapshot matched: an SOP, a skill-content rule, or a destructive-command rule promoted to block (the tier ships flagging, not blocking) |
SSO_GROUP | The workspace's SSO group policy does not clear this tool for the member, or the member's groups are unknown |
HELD | A hold rule matched: the call is held for a person's approval, and holdId names the hold |
PREREQUISITE_UNMET | A qualified requires_before (action:run_tests[ok] -> …) gates the call and is not met: no test run in the session, the last one failed, its result could not be read, files changed after it, the harness reports no tool results, or there is no session id |
OUT_OF_SCOPE | A file-changing call targets a path outside scope_paths under scope_paths_mode: enforce, or outside the workspace |
OFF_PLAN | plan_approval: strict is set and the tool is not a step of the session's approved plan |
PLAN_NOT_APPROVED | plan_approval: is set and the session has no APPROVED or EXECUTING plan, the approved plans could not be read, or there is no session id |
SERVER_BLOCKED | The MCP server is blocked in the MCP server registry |
SERVER_HELD | The MCP server changed its tools in a way scored high risk and waits for an owner or admin to approve it again |
SERVER_NOT_APPROVED | The workspace refuses MCP servers it has not approved (mcpDefaultPolicy: deny), and this one is not approved |
TOOL_DISABLED | The tool is switched off on this MCP server in the registry |
SERVER_NOT_ALLOWED | The MCP server is not on the workspace's mcpAllowedServers list |
POLICY_SNAPSHOT_UNVERIFIED | The policy snapshot on this machine failed its integrity check, so it admits no MCP server; the sync daemon restores the last verified snapshot |
COMMAND_TOO_LARGE | The command is over 256 KiB, or the tool arguments over 1 MiB, the most a gate evaluates; split the work into smaller calls |
GATE_DEADLINE | The gate did not finish deciding within its deadline, a second under its harness's hook timeout and at most 9 seconds, and refuses rather than let the hook timeout allow the call |
UNREADABLE_CALL | The hook received no tool call it could read, and refuses rather than allow a call it cannot evaluate |
GATE_CRASHED | The gate failed while deciding, and refuses rather than allow an unevaluated call; also a chaos run's INTUTIC_CHAOS_PLAN that is set but unreadable |
DECIDE_REFUSED | The rule asked its questions of the self-hosted decide engine and the answers made its when true |
DECIDE_ESCALATED | The answers to a decide rule's questions fell between its bands or disagreed across option orders, and the harness cannot ask a person, so the call is refused until a person decides |
GOVERNANCE_UNAVAILABLE | An enforcing decide rule could not be evaluated: its engine is not configured, is not self-hosted, did not answer in time, gave no usable probabilities or failed its known-answer canary, or more than four enforcing decide rules name the tool |
Post-tool outcome
A requires_before: action:run_tests[ok] -> action:deploy rule (gate body v23) is met only when the session's last test run passed and no file changed after it: a write, edit, delete or move tool, or a shell command that changes files (sed -i, perl -i, git apply, git checkout, git restore, git merge, git pull, git rebase, git stash pop, patch -p and the like), makes an earlier pass stale. A hook gate learns a run's result from the harness's post-tool hook, so the rule can be met only where that hook's payload carries an exit code or an error flag. Each row below was checked against a payload captured from the harness itself (services/sync-daemon/__tests__/fixtures/post-tool/); a harness with no captured payload gets no post-tool registration, and a qualified rule there refuses with the reason that the harness "reports no tool results Intutic can read".
| Harness | Post-tool event registered | What the gate reads | Session id |
|---|---|---|---|
| Claude Code | PostToolUse and PostToolUseFailure, matcher Bash | the success event's tool_response.stdout and stderr (no exit code: a run passes on a runner summary with passes and no failures); the failure event's error, whose first line is Exit code N (captured from Claude Code 2.1.233) | session_id |
| Gemini CLI (Antigravity's bash gate) | AfterTool, matcher run_shell_command | tool_response.llmContent, which carries Exit Code: N when the command failed (captured from Gemini CLI 0.36.0) | session_id |
| Goose | PostToolUse (registered before v23; it no longer re-runs the pre-tool gate) | nothing: not captured | — |
| Cursor, Windsurf, Codex CLI, Muse Code, GitHub Copilot, Cline, Grok Build, Google Antigravity, OpenHands, Hermes, OpenCode, Pi, OpenClaw | none | not observable: no payload captured | Cursor conversation_id, Cline taskId, Codex and Muse session_id, the in-process gates their host's session |
| n8n, Open WebUI | none | no tool results | — |
A post-tool invocation records the outcome in ~/.intutic/hooks/state/outcomes/ (one file per harness and session, under the protected .intutic/hooks) and always allows: the tool has already run. Runner summaries are read for pytest, jest, vitest, go test, cargo test, mocha and rspec; other runners decide by exit code only. A command that both tests and deploys (npm test && git push) is checked before its tests run, so it needs an earlier passing run. Gates older than v23 refuse the gated call through the rule's requires_outcome-fallback: line.
Scope and plan at the hook gates
Two SOP keys refuse at every hook gate since gate body v23 (SOPs with no roles: only: a hook gate knows no agent role):
scope_paths_mode: enforce— a file change (a write, edit, delete or move tool) whose target, made relative to the workspace root (the nearest.gitabove the call's working directory), is outside the SOP'sscope_pathsis refusedOUT_OF_SCOPE. A path outside the root is out of scope; shell commands are never read as paths.plan_steps_modestays gateway-only.plan_approval: required | strict— every call that is not a read (read,view,cat,glob,grep,ls,list_dir,search,todowrite…) is refusedPLAN_NOT_APPROVEDuntil the session's plan is APPROVED or EXECUTING; understrict, a call whose tool is not one of the plan's steps is refusedOFF_PLAN. The gates read~/.intutic/hooks/approved-plans.jsonl, which the sync daemon writes fromGET /api/v1/plans/approved(its key needstraces:read) with a digest, a workspace id and an expiry three sync intervals out; a cache that is missing, expired or fails a check is "no plan". With no control plane no plan can be approved, soplan_approval:refuses everything;intutic gates installandintutic doctorsay so. A harness that sends the gate no session id (GitHub Copilot, Grok Build, Windsurf, Google Antigravity) is refused there too. Gates older than v23 refuse through the rule's fallback line: upgrade them before enabling either key.
Vector D — Response Gate
The response gate (response_gate.rs, open-core, default-on) is the product's only harness-agnostic pre-execution tool gate: because every response byte passes through the proxy before the client sees it, a denied tool call is refused before it ever reaches the harness's tool runner — no per-harness hook, no harness cooperation. It understands the Anthropic (tool_use blocks), OpenAI chat-completions (tool_calls[]), and OpenAI Responses (function_call output items) wire shapes, on both streaming and non-streaming paths. When a call is withheld, the agent receives an explicit in-band message that the call never ran, so it does not blindly retry, and an SDK reads the refusal's code and rule from the x-intutic-refusal headers or, on a stream, a : intutic-refusal comment line (see clawde SDK).
It also enforces hook-rule policy guardrails — the same rules the Vector A gates enforce, served to the proxy as hook_rule: lines in the workspace SOP policy — on the tool name and the call's arguments, refusing under the rule's own id (guardrail.<id>) with the TOOL_DENIED code; a shadow rule is reported on the trace and refuses nothing. A rule scoped to workspace roles applies only when the virtual key's member holds one of them, and to every caller whose role cannot be read.
It is fail-closed within a deliberate scope: the gate is inert unless the active role has a non-empty tool deny list, the workspace has an SSO group policy that can refuse a tool, a SOP declares sql_guard:, or a hook-rule guardrail is in force; within that scope, a non-streaming body that will not parse as JSON is refused rather than forwarded (fail_closed, default true).
Known limits, stated precisely:
- Streams are gated on tool NAME only — with two exceptions. OpenAI sends
function.nameon the first delta and dribbles the arguments out as JSON fragments across later chunks, so the deny list decides on the name. The destructive-SQL guard (sql_guard:in a SOP, opt-in) needs the arguments, so while it is in force the lines of a shell-tool block are held back until the block ends, evaluated whole, then released in order or replaced by a refusal. A hook-rule guardrail that constrains arguments holds the blocks of the tools it names the same way; one that names only the tool decides on the first event. Text and other tools' blocks are never held. A stream cut off inside a held block drops the incomplete call, and arguments that complete as something other than JSON are refused under a rule that constrains them. - Locally-originated tool calls are invisible to it. A tool call that never traverses the proxy (e.g. issued directly by a local plugin or harness-internal logic) cannot be seen or withheld; Vectors A and C cover that surface.
- Gemini's native
functionCallparts are not matched on either path; a request the proxy forwards to Gemini in its native shape is ungated by this vector.
Worktree Coverage
Every project-tier governance file this product writes (Vectors A and C — the client hook/config files themselves, and the drift guard that watches them) is untracked by convention: SOP content is synced from the control plane, not committed to the repo. A git worktree add-created checkout has its own independent working tree, so none of that untracked content exists in a worktree by default.
intutic gates install writes project-tier files only into the workspace root it runs in. Other worktrees of the same repository are covered by the user-level registrations — the hooks in ~/.claude/settings.json, ~/.cursor/hooks.json, ~/.codex/hooks.json, ~/.gemini/settings.json, ~/.gemini/config/hooks.json, ~/.codeium/windsurf/hooks.json and the like apply in every directory — but not by project-level files such as .claude/settings.json, .opencode/plugins/ or the rules files. For a harness whose gate is project-level only (OpenCode, Cline, OpenHands), run that harness from the main checkout, or copy its generated files into the worktree.
Coverage Matrix
| # | Harness | Hook (A) | Proxy (B) | Drift Guard (C) | Self-Mod Risk | Notes |
|---|---|---|---|---|---|---|
| 1 | Claude Code | ✅ PreToolUse | ✅ | ✅ settings.json | Medium | Full coverage |
| 2 | Cursor | ✅ hooks.json (project + user; machine-wide via enterprise install) | ⚠️ own API keys only (OpenAI base-URL override) | ✅ 3 paths | HIGH | Gate on beforeShellExecution, beforeMCPExecution and preToolUse (Write|Delete), fail-closed; Cursor-hosted models are not proxy-routable — see §Gaps |
| 3 | Windsurf | ✅ Shell/write/MCP hooks | ✅ TLS MITM | ✅ 3 hooks.json + settings.json | HIGH | Cascade traffic via TLS MITM proxy |
| 4 | Cline | ✅ .clinerules/hooks/PreToolUse | ⚠️ manual (Cline settings panel) | ✅ .clinerules/ | HIGH | File hook, refuses via {"cancel":true} on stdout; VS Code extension needs "Enable Hooks" on |
| 5 | Roo Code | ❌ No hook system | ⚠️ manual (Roo Code settings panel) | ✅ AGENTS.md section | HIGH | Rules (an AGENTS.md section) only; enforcement is the proxy and the MCP governance proxy |
| 6 | Aider | ❌ No pre-edit hook | ✅ openai-api-base | ✅ .intutic/aider-sops.md | HIGH | test-cmd/lint-cmd suppressed. The only native mechanism is the opt-in --git-commit-verify pre-commit hook — post-edit and blind to /run, so no gate is built on it |
| 7 | OpenHands | ✅ .openhands/hooks.json | ✅ llm.base_url | ✅ hooks.json | HIGH | Shell script hook (exit 2 blocks) in the SDK's HookConfig schema. OpenHands runs a call whose hook times out and has no setting to refuse instead, so the gate refuses at its own deadline. Earlier versions wrote a flat list the SDK cannot load |
| 8 | Codex CLI | ✅ codex-check.js | ✅ ~/.codex/config.toml (openai_base_url) | ✅ AGENTS.md section, both hooks.json | Low | Blocking hook (exit 2) registered in ~/.codex/hooks.json + <repo>/.codex/hooks.json; WHERE argPattern rules enforced against the serialized tool input. Live-verified (codex-cli 0.147.0, 2026-08-10): a real codex exec session fired the hook, the unpinned kubectl apply was refused before execution with the rule named, and the pinned/benign commands ran |
| 9 | n8n | ⚠️ Workflow-level gate | ✅ API-configurable | ✅ ~/.intutic/hooks/n8n-governance-hook.js | Medium | n8n-governance-hook.js via EXTERNAL_HOOK_FILES (manual, deployment-side): workflow.preExecute receives the full Workflow and throws to abort — genuinely blocking, but per workflow, not per tool call. Node type ≈ tool name; argPattern matches the serialized node parameters. Live-verified (official n8n image, 2026-08-10): a running server with EXTERNAL_HOOK_FILES aborted the offending workflow (HTTP 500, error naming node and rule) and passed the clean one — after two live-only bugs were found and fixed (the real workflow.preExecute passes nodes as an object keyed by name, not the documented array; and the product snapshot's space-padded patterns cannot match dot-namespaced node types) |
| 10 | Continue | ❌ no hook that runs | ✅ apiBase in config.yaml | ✅ .continue/rules/intutic-governance.md | Low | Governed by the proxy (apiBase on its OpenAI/Anthropic models) and the always-apply rules file. The IDE extension has no hook system, and the CLI (cn) loads PreToolUse hooks from settings.json but never fires them: firePreToolUse has no caller outside tests at continuedev/continue main 5522c6f, and PR #11043, which would have wired it, was closed unmerged on 2026-03-24. Intutic no longer registers a gate there (GateKind: 'none'); disconnect removes the one earlier versions wrote |
| 11 | Goose | ✅ Plugin PreToolUse | ✅ OPENAI_HOST / ANTHROPIC_HOST | ✅ Immutable plugin | HIGH | chmod 444 + OS immutable flags; on_failure: "block" on the gate's action. Earlier versions wrote a hooks.json shape Goose skips with a warning |
| 12 | Antigravity (Google Antigravity and Gemini CLI) | ✅ antigravity-cli-check.js, antigravity-check.sh | ❌ Gemini API not served by the proxy | ✅ GEMINI.md section, ~/.gemini/settings.json, ~/.gemini/config/hooks.json | Medium | Google Antigravity (app, IDE, CLI): blocking PreToolUse hook ({"decision":"deny"} on stdout) in ~/.gemini/config/hooks.json. Gemini CLI: blocking BeforeTool hook (exit 2) in ~/.gemini/settings.json. Drift guard restores both. The two gates report as antigravity and gemini-cli, so gate health and the AI inventory track each product found on the machine; it stays one harness |
| 13 | Claude Desktop | ❌ No hook system | ❌ Locked to Anthropic | ✅ claude_desktop_config.json | Medium | MCP servers in claude_desktop_config.json wrapped by the MCP governance proxy; drift guard detects rogue MCP servers |
| 14 | Open-WebUI | ⚠️ Prompt-level filter | ✅ Docker env | ✅ ~/.open-webui/intutic-governance-filter.py (the file Intutic writes; the copy pasted into Open WebUI is not watched) | Low | intutic-governance-filter.py can refuse (Python raise), but filters see a prompt, not a tool call — only snapshot rules marked block refuse; the compiled floor flags |
| 15 | OpenClaw | ✅ before_tool_call plugin (fail-closed) | ⚠️ manual (models.providers.<id>.baseUrl, not written) | ✅ the plugin + openclaw.json | Medium | Standalone plugin listed in plugins.load.paths; refuses with { block: true, blockReason } |
| 16 | Hermes | ✅ hermes-check.sh | ⚠️ manual (not written) | ✅ ~/.hermes/config.yaml | Medium | A hooks.pre_tool_call shell hook in ~/.hermes/config.yaml runs ~/.intutic/hooks/hermes-check.sh (exit 2 blocks) with fail_closed: true. Earlier versions wrote hooks.preToolUse, which Hermes skips as an unknown event |
| 17 | Pi | ✅ tool_call extension | ✅ ~/.pi/agent/models.json (anthropic, openai) | ✅ the extension | Medium | Extension in ~/.pi/agent/extensions/ (user level); refuses with { block: true, reason } |
| 18 | GitHub Copilot | ⚠️ Preview hooks | ❌ | ✅ copilot-instructions.md, .intutic/hooks/github-copilot-check.js, .github/hooks/intutic-governance.json, ~/.copilot/hooks/intutic-governance.json; chat.useHooks and chat.hookFilesLocations in VS Code settings | Low | github-copilot-check.js (exit 2) via VS Code agent hooks (.github/hooks/*.json + ~/.copilot/hooks) — a Preview feature whose format may change; the gate refuses payloads it does not recognise, so a shift fails closed. Instructions file still merged. argPattern rules enforced. Contract re-verified 2026-08-10 against the live VS Code docs (envelope, exit-2, both file locations, chat.hookFilesLocations); an end-to-end block inside a Copilot-subscribed agent session remains unverified — it needs an interactive editor with an entitlement |
| 19 | LangGraph | ✅ SDK-side (Python raise) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | Gate lives in the developer's code via intutic_clawde.gate (guard_tools / @guard), not a generated hook file — it sees the tool call's full arguments, so argPattern rules apply; traces attributed via x-intutic-harness |
| 20 | Muse Code | ⚠️ muse-check.js | ⚠️ env-var only | ✅ 3 paths | Medium | Blocking hook (exit 2, ASSUMED contract) registered in <repo>/.muse/hooks.json AND via the pre-approved managed_hooks_path tier (~/.config/muse/intutic-managed-hooks.json, referenced from ~/.config/muse/settings.json); covers both PreToolUse and PermissionRequest. No persistent proxy base-URL setting was confirmed — routing is META_API_KEY/launcher-flag only. The muse binary could not be installed to live-verify any of this (beta product, no public release channel found) |
| 21 | Grok Build | ✅ grok-check.js | ✅ config.toml [model.*] | ✅ .grok/hooks, config.toml, trusted_folders.toml | Medium | Blocking hook, PreToolUse with no matcher, registered in .grok/hooks/intutic-governance.json (project) and ~/.grok/hooks/intutic-governance.json (user). Confirmed blocking contract: {"decision":"deny","reason":"..."} on stdout, exit 0 — a different stdout shape from Cline's {"cancel":true}, so this harness carries its own stdout-decision-deny contract rather than reusing theirs. Double-gating note: Grok Build also natively reads .claude/settings.json and .cursor/hooks.json if present, so a workspace already running either of those gates fires BOTH on a Grok Build tool call — expected, additive, not a bug (see The gate backstop for the same "both layers firing is expected" posture applied to the proxy+gate combination). Not live-verified: Grok Build was not installable in the environment this harness was implemented in, so the per-file hook-registration schema (event/command/timeout fields) is this integration's own reasonable design against the confirmed facts, not a byte-for-byte match against a real install |
| 22 | dsh Preview | ⚠️ @intutic/gate/dsh (Cordis plugin) | ⚠️ llm-deepseek (default route) redirected; the proxy forwards DeepSeek's API model ids to DeepSeek's own API (account route not redirectable) | ✅ $DSH_HOME/profiles (every profile's cordis.patch.yml) | Medium | DeepSeek's "dsh" (developer preview since 2026-08-13; last verified against 0.2.0-rc.2) is plugin-first (Cordis) — the gate is a real, checked-in TypeScript module (packages/gate-js/src/dsh.ts), not a generated shell/JS string. Registers on the tools/pre-execute Cordis waterfall, returning {kind:'deny', reason} to veto or calling next() to allow (waterfall-reject) — live-verified on 2026-10-03: a blocked bash call never ran and the model received the reason as an error tool result. Merge-written into EVERY existing $DSH_HOME/profiles/*/cordis.patch.yml (dsh has no single default profile — --profile <name> is required on every invocation), together with a baseURL override on the llm-deepseek entry (dsh 0.2 keeps live config there, not in settings.yaml). Known gaps: the plugin needs a manual dsh plugin --profile <name> add @intutic/gate per profile, and until then dsh warns on stderr and runs ungoverned (permanent, accepted step); a machine where dsh has never been run has no profile to register into until the first dsh --profile <name> run; and the signed-in DeepSeek account route cannot be redirected. The proxy's DeepSeek route (deepseek-chat, deepseek-reasoner and deepseek-flash to DEEPSEEK_UPSTREAM_URL, Messages requests unchanged to its /anthropic API) is tested against a mock upstream only |
| 23 | Xirp | ⚪ N/A — delegated | ⚪ N/A — delegated | ⚪ N/A — delegated | Low | NOT itself an AI agent — a macOS orchestrator that spawns Claude Code/Codex/Gemini CLI, each in its own tmux session + git worktree, preserving each wrapped harness's native, unmodified config (per Xirp's own FAQ). No gate/config of its own to register: a tool call inside a Xirp session is governed by whichever wrapped harness's own row above is already running. Sessions run in their own git worktree, where the wrapped harness's user-level hooks apply but its project-level files do not — see Worktree Coverage. Not live-verified: macOS-only beta with no installable artifact in this environment; detection paths (~/.xirp, Xirp.app) are this integration's own reasonable convention, not confirmed against a real install |
| 24 | Agentic Orchestrator | ⚪ N/A — delegated | ⚪ N/A — delegated | ⚪ N/A — delegated | Low | NOT itself an AI agent — DoorDash's open-source (Apache-2.0, doordash-oss/agentic-orchestrator) desktop app + CLI (agentico) that spawns Claude Code/Codex/OpenCode, each in its own git worktree under ~/.agentic-orchestrator/worktrees/. No gate/config of its own to register: a tool call inside a session is governed by whichever wrapped backend's own row above is already running — including OpenCode (row 43) since September 2026. Live-verified, unlike Xirp: the real released binary (v0.152.0, darwin_arm64) was downloaded and run directly during this integration's research (--version, --help, server --help) — the wrapped-backend list, the --config/--state-dir defaults, and the worktree-per-feature model are all confirmed against the actual artifact and the project's own README, not merely this codebase's convention for comparable tools |
| 25 | eve Preview | ⚠️ @intutic/gate/eve (per-tool/connection approval policies) | ⚠️ direct-provider models only | ✅ .env.intutic | Medium | Vercel's filesystem-first durable-agent framework (npm eve, pre-1.0 preview) — the gate is a real, checked-in TypeScript module (packages/gate-js/src/eve.ts), not a generated script: intuticApproval() / intuticConnectionApproval() return AI SDK 7 approval statuses from eve's confirmed per-tool/per-connection approval policy surface ({type:'denied',reason} vetoes before execute() runs; verified against a pinned real install of eve@0.71.0, shipped .d.ts + runtime defineTool/defineMcpClientConnection/defineHook machinery). No agent-level default approval field exists (verified) — coverage is exactly the tools/connections the developer attaches the policy to, eve's own documented multi-tenant pattern. Observe-only intuticAuditHooks() maps eve's approval.candidate/approval.settled events onto tool_allowed/tool_blocked/tool_flagged (request-scoped: those events carry no tool name). Proxy caveat: eve's default model routing is the Vercel AI Gateway, whose wire protocol the Intutic proxy does not parse — only in-code direct-provider models (withIntuticProxy(...)) are proxy-governed; no env-var base-URL override exists. Detection is a compound check (eve dep + agent/ directory). Preview: pinned to a verified eve release, as dsh is. Live-run on a self-hosted server only: eve build + eve start with a scripted model, on 0.39.1 and 0.71.0; a Vercel deployment and a real model provider were not exercised |
| 26 | AI SDK Harness | ⚠️ @intutic/gate/harness approval-flow responder + intuticSandboxBootstrap() (Tier A1 only) | ❌ N/A — sandbox traffic never crosses the local proxy | ✅ .env.intutic | Low | Vercel's @ai-sdk/harness HarnessAgent runtime — tools execute server-side inside a Vercel Sandbox microVM, not on the machine running Intutic. Custom host-executed tools get a real per-call gate via intuticStaticApprovals() + intuticApprovalResponder() (every custom tool marked 'user-approval', each pause answered with a real Gate.guard() verdict). recommendedHarnessSettings() now defaults to inactiveTools: ['bash'], removing bash from the tool set entirely (confirmed via real shipped dist/index.js, not just types) — a stronger mitigation than approval-gating since the tool is never callable; opt out with filterBash: false. Other built-in sandbox tools (read/write/edit/glob/grep) are governed by the coarse permissionMode setting — defaults to 'allow-all'; write/edit pause per call only under 'allow-reads', and read-kind tools never pause. intuticSandboxBootstrap() additionally injects a Tier A1 policy-snapshot hook + .claude/settings.json directly into the sandbox filesystem via sandboxConfig.onBootstrap — a real but strict subset of a laptop gate (no SOP tier, review-hold, or event draining), Claude Code only; its bootstrap-channel mechanics were live-verified against a real Vercel Sandbox (files land, hook genuinely blocks/allows), and the bridge's own PreToolUse dispatch plus a stop/resume cycle were verified locally in a Docker sandbox; a true snapshot publish/restore, live setNetworkPolicy enforcement and the Grok Build path remain unverified. Sandbox network egress is governed by the sandbox's own networkPolicy (host/CIDR allow-deny), not Intutic DLP — that traffic never reaches the local proxy at all. Self-Mod Risk is Low precisely because tool execution happens off-host, in a microVM the local config-drift guard cannot reach either way. Detection is @ai-sdk/harness, any @ai-sdk/harness-*, or any @ai-sdk/sandbox-* in package.json. See the integration page |
| 27 | AI SDK Workflow | ✅ @intutic/gate/workflow's withIntuticApproval() (per-tool needsApproval) | ⚠️ in-code only, same limitation as Vercel AI SDK | ✅ .env.intutic | Medium | Vercel's @ai-sdk/workflow WorkflowAgent on the Workflow DevKit. The veto surface is per-tool needsApproval — the agent itself carries no approval option — and a gated call pauses durably, potentially resuming hours later. The durable runtime retries thrown errors by default, so a plain denial would be replayed to max attempts; this adapter's refusals carry error.name === 'FatalError' (matching FatalError.is()) so the runtime aborts instead of retrying. LLM-egress routing is in-code only via withIntuticProxy() — same limitation as the Vercel AI SDK row — and is off the path entirely when the workflow is deployed to run on Vercel's own infrastructure rather than the developer's machine. Detection requires @ai-sdk/workflow in package.json (the unscoped workflow package alone is not a trigger). See the integration page |
| 28 | Strands Agents | ✅ SDK-side (intutic_clawde.gate.adapters.strands, BeforeToolCallEvent.cancel_tool — MCP tools included) | ⚠️ Anthropic/OpenAI/LiteLLM providers only | ✅ .env.intutic | Medium | AWS's open-source agent framework (Bedrock AgentCore's default framework). The SDK tool gate is the enforcement point regardless of LLM provider. Strands' default provider, BedrockModel, calls Bedrock SigV4-signed — the Intutic proxy cannot re-sign SigV4 requests, so no base-URL env var can route it through the proxy (same for the SageMaker provider); this is the first framework in the SDK-gated family whose default egress is unroutable. Anthropic/OpenAI/LiteLLM providers remain proxy-routable via .env.intutic's base-URL vars. Detection is strands-agents in a Python manifest. See the integration page |
| 29 | Anthropic Managed Agents | ✅ SDK-side session-confirmation responder (IntuticSessionConfirmer, TS + Python) | ⚪ N/A — Anthropic hosts the session; no local LLM egress to route | ⚪ N/A — no local config file, no HarnessType, not detected by intutic init/intutic gates install | Low | Architecturally unlike every other row in this table: the customer's process does not run the tool-call loop — Anthropic hosts a "session" that emits an event stream and the customer's backend answers it. agent.tool_use/agent.mcp_tool_use calls the server evaluated to evaluated_permission: "ask" (e.g. permission_policy: always_ask) pause the whole session until the backend sends user.tool_confirmation; IntuticSessionConfirmer answers that pause with a real Gate.guard() verdict — a genuine pre-execution veto, not audit-only. agent.custom_tool_use (the customer's own tools) has no pause concept and is gated separately via wrapManagedAgentsCustomTool(s) (TS) / @guard before @beta_tool (Python). A tool configured always_allow is an architectural ceiling, not a bug — the call never reaches the backend as an event to answer at all. Listed here for governance-coverage completeness even though it carries no HarnessType — see the warning on its own integration page for why auto-detection was deliberately not built |
| 30 | AWS Bedrock AgentCore | ⚪ Runtime: N/A — delegated to whichever framework adapter the hosted code uses. Gateway: tools/agentcore-interceptor Lambda | ❌ Bedrock's own model-invocation traffic (Converse/InvokeModel) is SigV4-signed, not proxyable | ⚪ N/A — Runtime's writeConfig is a no-op (HarnessType: agentcore-runtime exists so intutic status/intutic init can report the deployment target) | Low | Two structurally different integrations under one row, per the integration page: Runtime hosts the customer's own agent code (any framework) unchanged — governance is whichever already-supported framework SDK gate that code uses (Strands, LangGraph, CrewAI, ...), same as anywhere else that framework runs; if the Runtime-hosted code uses no framework this registry supports (raw boto3), coverage is genuinely zero. Gateway is a deployed AWS resource, not something on a developer's laptop — like QM, it has no HarnessType and is never detected by intutic init; its coverage is the tools/agentcore-interceptor Lambda, attached as a REQUEST interceptor, calling POST /api/v1/integrations/agentcore/gateway-check. Bedrock's own model-invocation traffic is SigV4-signed and cannot be transparently redirected through the Intutic proxy on either shape. |
| 31 | LangChain | ✅ SDK-side (intutic_clawde.gate.adapters.langchain.IntuticMiddleware, LangChain v1.x AgentMiddleware.wrap_tool_call) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | LangChain v1.x's documented wrap_tool_call(request, handler) veto point; on deny, IntuticMiddleware returns a ToolMessage(status="error") without calling handler — the tool body never runs. Pre-1.0 LangChain, or plain tool objects: the framework-agnostic guard_tools() helper duck-types .func/._run without importing langchain. Detection also covers LangChain.js (langchain npm) for reporting purposes only — this adapter's env-writer and SDK gate are Python-only, matching intutic_clawde.gate.adapters.langchain; LangChain.js has no dedicated adapter — wrap its tools with @intutic/gate's generic wrapTools(). See the integration page |
| 32 | CrewAI | ✅ SDK-side (intutic_clawde.gate.adapters.crewai.install, CrewAI's global before_tool_call hook registry) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | CrewAI (v1.15.3+) documents a before_tool_call hook returning False to block, True/None to allow. Because CrewAI's own dispatcher swallows any hook exception that is not its internal abort signal and treats the call as allowed, this adapter deliberately returns False on any exception (not only a confirmed IntuticGateRefusal) — a fail-closed posture proven necessary by driving a real crewai==1.15.16 install. Known transitive vulnerabilities (2026-08-19, updated 2026-10-21): crewai depends on chromadb, and every release up to 1.5.9 (the latest) carries four unpatched CVEs — CVE-2026-45829, CVE-2026-45833 (both critical, code injection), CVE-2026-45830, CVE-2026-45831 (both high, cross-tenant access-control gaps) — relevant only if you separately run a ChromaDB server reachable from an untrusted network; this integration never starts one. crewai also installs uv 0.11, below the fix for GHSA-hq85-5cpj-mr62 (a compromised root CA in uv's bundled trust store), which crewai's own commands use to download packages; this integration never runs uv. See the integration page |
| 33 | AutoGen | ✅ SDK-side (intutic_clawde.gate.adapters.autogen_workbench.IntuticWorkbench, a Workbench.call_tool veto for AssistantAgent's in-process tool calls; intutic_clawde.gate.adapters.autogen.IntuticInterventionHandler, InterventionHandler.on_send, for runtime-routed FunctionCall messages) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | Two veto points, because Microsoft AutoGen (autogen-core/autogen-agentchat/autogen-ext, 0.4+) has two tool-call paths. AssistantAgent — the class essentially every AutoGen application is built on — runs its tool calls through its Workbench in-process (_execute_tool_call → wb.call_tool(...)), never through the runtime; IntuticWorkbench wraps that workbench and returns ToolResult(is_error=True) on a deny, so the body never runs and the model reads the block. on_send covers the other path, custom AgentRuntime/RoutedAgent systems dispatching FunctionCall messages by hand. Not governed: handoff tools (they bypass the workbench and execute no user tool); a wrapped StaticStreamWorkbench loses its intermediate streamed events. autogen_core.ToolException does not exist at autogen-core==0.7.5 — DropMessage is the runtime-path veto. See the integration page |
| 34 | AG2 | ✅ SDK-side (intutic_clawde.gate.adapters.ag2.IntuticMiddleware, BaseMiddleware.on_tool_execution) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | AG2 (ag2==1.0.2+) is a from-scratch fork/continuation of pre-Microsoft AutoGen with no shared API surface — a different framework from row 33 above, despite the name; do not confuse the two. BaseMiddleware.on_tool_execution(call_next, event, context) is AG2's own documented veto point, confirmed by reading ag2/middleware/base.py, ag2/tools/executor.py, and ag2/tools/final/function_tool.py against a real install; on deny it returns ToolErrorEvent.from_call(...) without calling call_next. Detection uses a boundary-aware regex (not a plain substring) to avoid false-positiving on strings like "flag2". The adapter fails closed on any exception inside on_tool_execution; AG2's own ToolExecutor dispatcher also turns an exception escaping the hook into a ToolErrorEvent (confirmed by driving the real dispatcher), so the catch-all mainly gives the model a clear BLOCKED message. See the integration page |
| 35 | Google ADK | ✅ SDK-side (intutic_clawde.gate.adapters.google_adk.IntuticPlugin, ADK's before_tool_callback) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | ADK documents before_tool_callback — returning a non-None dict skips the real tool call and that dict becomes the synthetic result. Both of ADK's registration surfaces are covered: IntuticPlugin (global, via App(plugins=[...]), every tool call across the app) and intutic_before_tool_callback (per-agent fallback for callers not using App(plugins=[...]), LlmAgent(before_tool_callback=...)) — the two use different keyword conventions on ADK's own call sites (tool_args= vs args=) and both adapter functions match theirs exactly. On deny, both return a synthetic {"status": "error", "error": "[Intutic Governance] BLOCKED: ...", ...} dict instead of letting the tool run. See the integration page |
| 36 | OpenAI Agents SDK | ⚠️ SDK-side, both ecosystems (Python: intutic_clawde.gate.adapters.openai_agents.intutic_tool_guardrail; TS: @intutic/gate/openai) | ✅ OPENAI_BASE_URL (zero-code — both ecosystems' underlying openai client reads it automatically) | ✅ .env.intutic | Medium | Covers both the Python (openai-agents) and TypeScript (@openai/agents) ecosystems — Python's documented @tool_input_guardrail (intutic_tool_guardrail, attached via function_tool(tool_input_guardrails=[...])) and TypeScript's @intutic/gate/openai (wrapAgent()/intuticToolGuardrail(), verified against @openai/agents@0.16.1); on deny both hand a [Intutic Governance] BLOCKED: ... message back to the model in place of a tool result. Structural gap: hosted/server-side tools (webSearchTool, fileSearchTool, codeInterpreterTool, image generation, hosted-environment shellTool) execute at OpenAI and are "not gateable client-side, by anyone" — no guardrail, approval hook, or wrapper ever sees them. computerTool's only hook is a coarse needsApproval predicate (no refusal message; an approving handler overrides the gate). An unwrapped hostedMcpTool defaults to require_approval: 'never' — entirely ungated until wrapped via wrapAgent. Separately: @openai/agents ships tracing on by default, whose exporter POSTs tool inputs/outputs to a hardcoded api.openai.com endpoint that ignores OPENAI_BASE_URL; installOpenAiGate() disables it by default (OPENAI_AGENTS_DISABLE_TRACING=1). See the integration page |
| 37 | Pydantic AI | ⚠️ SDK-side (intutic_clawde.gate.adapters.pydantic_ai.guard_agent/IntuticWrapperToolset, WrapperToolset.call_tool override) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | IntuticWrapperToolset subclasses Pydantic AI's own WrapperToolset (built for exactly this intercept-before-delegating shape); on deny it raises ModelRetry(...), confirmed by reading pydantic_ai/tool_manager.py — the model sees the refusal in place of a tool result and the wrapped toolset's call_tool never runs. Coverage gap: guard_agent wraps Agent(toolsets=[...]) entries (_user_toolsets/_dynamic_toolsets) but deliberately leaves agent._function_toolset — the toolset backing the commonly-used @agent.tool decorator / Agent(tools=[...]) — untouched; those need @guard or an explicit FunctionToolset(tools=guard_tools([...])) to be covered. guard_agent also reaches into private attributes (confirmed present at pydantic-ai-slim==2.31.1) with no public replacement point — a future release could silently turn it into a no-op. See the integration page |
| 38 | smolagents | ⚠️ SDK-side, split by agent class (guard_tools/@guard for ToolCallingAgent; intutic_clawde.gate.adapters.smolagents.IntuticPythonExecutor for CodeAgent) | ✅ base_url / intutic exec | ✅ .env.intutic | Medium | ToolCallingAgent's discrete tools are gated exactly like every other framework (guard_tools, extended to duck-type smolagents' .forward so .name/.inputs schema survives). CodeAgent is structurally different: a "tool call" is a whole chunk of model-generated Python handed to a PythonExecutor, so IntuticPythonExecutor gates the generated code string ({"command": code_action}) before execution, raising smolagents.local_python_executor.InterpreterError on deny — driven live against smolagents==1.26.0. This closes real coverage CodeAgent had none of before, but is narrower than argument-level gating: runtime-constructed strings evade argPattern, conditional/unreached branches are judged the same as always-taken ones, and tools invoked by the running code via send_tools are a real in-process call invisible to this pre-execution text check. No finer-grained veto point exists in smolagents to close this. See the integration page |
| 39 | Mastra | ⚠️ SDK-side (@intutic/gate/mastra's intuticHooks(), Agent.hooks.beforeToolCall) | ✅ baseURL (in-code provider construction) | ✅ .env.intutic | Medium | beforeToolCall returns { proceed: false, output } on deny — Mastra skips the tool's real execute entirely, for both natively-defined and MCP-sourced (@mastra/mcp) tools alike (both merge into the same tool record before hooks wrap it); verified against @mastra/core@1.59.0 and 1.74.0. Known bypass, Mastra's own documented behavior, not an Intutic defect: if any caller of .generate()/.stream() passes its own call-level hooks option — even {} — Mastra replaces the agent-level hooks wholesale rather than merging, silently disabling this gate for that call with no error or warning; callers that pass call-level hooks must compose intuticHooks().beforeToolCall into them explicitly. See the integration page |
| 40 | Vercel AI SDK | ✅ SDK-side (@intutic/gate/vercel's intuticToolApproval(), the ai package's toolApproval option) | ⚠️ in-code only — no environment-variable LLM routing exists for this framework | ✅ .env.intutic | Medium | toolApproval (accepted by generateText, streamText, ToolLoopAgent) is the ai package's (v6+) documented pre-execution veto point; on deny it resolves { type: 'denied', reason } and the tool's real implementation never runs, on allow it resolves 'not-applicable', composing cleanly with any other approval mechanism (e.g. a human-in-the-loop 'user-approval' status) or with Vercel's own first-party @ai-sdk/policy-opa Rego-backed adapter. Unlike almost every other harness in this catalog, LLM egress cannot be routed via env var: providers (@ai-sdk/openai, etc.) are constructed directly in code, so .env.intutic's base-URL vars are written but inert unless every provider-construction call site uses withIntuticProxy() explicitly. See the integration page |
| 41 | TrueForge Embedded only | ⚠️ SDK-side, ASYNC BATCH responder (@intutic/gate/trueforge's intuticApprovalResponder(), TrueForge's tool.approval_required/user.tool_approval turn contract) — see the note below distinguishing this from a synchronous per-call callback | ⚠️ in-code only — VercelAIProviderConfig.baseUrl, no environment-variable LLM routing | ✅ .env.intutic | Medium | TrueFoundry's open-source agent-runtime library (github.com/truefoundry/trueforge, MIT), embedded via @truefoundry/trueforge-core directly into another team's Node process — this row covers ONLY that embedded-library deployment mode. Confirmed against a real install (@truefoundry/trueforge-core@0.1.4, shipped .d.ts files read directly, not assumed): there is NO synchronous in-process approval callback anywhere in the package, unlike Mastra's beforeToolCall or the Vercel AI SDK's toolApproval — every .d.ts was grepped for approvalResolver/onApproval/approvalHandler/resolveApproval with zero matches, and ITurnResourceResolver (the surface this integration's authoring plan specifically pointed at) carries no tool-call veto hook at all. The ONLY approval mechanism, embedded or not, is the same async turn/event contract TrueForge's standalone server exposes over HTTP: a turn pauses on a tool.approval_required required action naming the pending call only by {id, source_event_id} (no tool name or arguments travel on the event), and answering it means starting a NEW turn carrying a user.tool_approval input item. intuticApprovalResponder() evaluates a batch of already-resolved pending requests through Gate.guard() and produces those items — but resolving source_event_id back to a real tool name and arguments, and driving the turn/event loop itself, is necessarily the embedding host's own hand-written code, not something this adapter configures or verifies. This Hook (A) mark is deliberately distinct from the plain ✅/⚠️ this catalog uses for a synchronous SDK callback (Mastra, Vercel AI SDK, AI SDK Harness's continuation-based responder is the closer precedent) — it is real, pre-execution, fail-closed coverage, but only for the portion of the orchestration loop the embedding host actually wires up correctly. TrueForge run as its own standalone/hosted server is explicitly NOT covered by this row — nobody embeds an Intutic gate into a third-party OSS server process; that deployment mode needs an external Intutic-operated bridge service, which is HarnessType.TRUEFORGE_SERVER, row 42 below. Also not covered: TrueForge's own require_approval_for_tools MCP tool-selector policy (@all/@write/@destructive/a specific tool) controls which tool calls even emit a tool.approval_required pause in the first place — an operator who leaves it narrow gets tool calls that never pause and that this gate never sees, TrueForge's own default-scoped design, not an Intutic gap, but a real coverage-scoping factor. See the integration page |
| 42 | TrueForge Server Standalone/hosted | 🌉 OUT-OF-PROCESS bridge (services/trueforge-bridge, GateKind: 'bridge') reacting to the same tool.approval_required/user.tool_approval turn contract row 41 documents — see the note below for why this mark is deliberately NOT the same ✅/⚠️ shape as row 41's or any other Hook (A) mark on this page | ⚠️ in-code only — PUT /api/v1/settings/model-providers, no environment-variable LLM routing | ⚪ N/A — no config file exists to drift-guard (not detected by intutic init; see Notes) | Medium | TrueForge (github.com/truefoundry/trueforge, MIT) run as its OWN standalone or hosted server — npx @truefoundry/trueforge, its Docker Compose stack, or its Helm chart — rather than embedded into another team's process (row 41). Not detected by intutic init's repo scan at all: this is an operator-configured deployment, not a package.json dependency of whatever repo the CLI happens to run against, so there is no writeConfig step and no on-disk file for the Drift Guard (C) column to watch. This Hook (A) mark is deliberately a different symbol, not a reused ✅/⚠️, stated plainly rather than left implicit: every OTHER Hook (A) mark in this catalog — including row 41's own SDK-side async responder — runs co-resident with or embedded in the governed harness's own process. This one does not. services/trueforge-bridge is a wholly SEPARATE Intutic-operated process, reacting to a PULL-ONLY event stream (an SSE subscription per turn, reopened after a 2-second backoff when it drops — there is no webhook; TrueForge never pushes anything to an external system) rather than running inside the harness at all. That is a strictly weaker trust boundary than anything else on this page carries a Hook (A) ✅/⚠️ for: a bridge that is down, disconnected, or slow to reconnect misses pauses for that window, in a way no in-process gate can. When connected and reacting, the check itself is real: SOP argPattern rules (packages/gate-js's soprules.ts, reused directly) then POST /api/v1/hook-gate, fail-closed on any transport/parse error, identical governance logic to every other JS/TS-native row on this page. The same require_approval_for_tools/MCP-selector caveat row 41 documents applies here too, and matters MORE: row 41's embedding host at least controls its own approval-loop wiring; here there is no embedding host at all, only whatever selector each MCP server on the operator's TrueForge instance happens to be configured with — a narrow/default selector means some tool calls never pause and are invisible to the bridge, full stop. See the integration page |
| 43 | OpenCode | ✅ .opencode/plugins/intutic-governance.js + intutic-governance/index.js (in-process plugin, throw to refuse) | ⚠️ provider.<id>.options.baseURL in opencode.json (documented, not written) | ✅ .opencode/plugins | Medium | OpenCode (opencode-ai 1.x, @opencode/cli 2.x) has no hook file: it loads plugins into its own process from .opencode/plugins/*.js and runs tool.execute.before (1.x) / tool execute.before (2.x) before every tool call — built-in, task and MCP tools alike — and a thrown Error is returned to the model as the tool error with the tool never run. Intutic writes one generated plugin module carrying the shared gate body with a throw block contract (floor + snapshot + WHERE rules + audit line, same as codex-check.js), in both layouts the two lines discover (1.x globs flat files, 2.x reads <name>/index.js directories); both hook shapes live in the same module. Fail-closed is local (a gate fault is rethrown as BLOCKED); no process-level handlers, since it runs inside the user's editor. Only the mcp block of opencode.json is written: each server is proxy-wrapped (project and global file, plain JSON only; an oauth remote is left alone), and the plugin composes OpenCode's <server>_<tool> MCP ids into mcp__<server>__<tool> for the allowlist using the longest configured server name. Egress stays the user's (provider.*.options.baseURL/OPENCODE_CONFIG_CONTENT, static permission map optional). Detected by .opencode/, opencode.json{,c}, the user config dir, or opencode/opencode2 on PATH. Live-verified on 1.x (opencode-ai@1.18.32 installed from npm, a local Ollama model, 2026-09-23): the plugin announced itself on load, a bash call naming .intutic/hooks/keep.txt came back as status: "error" carrying [Intutic Governance] BLOCKED: … [path.intutic_hooks], the model read the block, and the audit line landed in hook-events.jsonl. 2.x not live-verified: @opencode/cli@2.0.14 was installed and its first run showed the flat file is NOT loaded by 2.x (its shell calls ran ungoverned), which is why the <name>/index.js layout the 2.x docs specify is written too — but every later 2.x run in this environment stalled before opening a session, so that layout and the setup(ctx) shape are pinned only by the registry driver, against the documented API. |
| 44 | Microsoft Agent Framework | ✅ SDK-side (intutic_clawde.gate.adapters.agent_framework.IntuticFunctionMiddleware, a FunctionMiddleware that never calls call_next() on a deny) | ⚠️ OpenAI/Anthropic clients via OPENAI_BASE_URL/ANTHROPIC_BASE_URL; Azure OpenAI/Foundry clients use their own endpoint settings, not routed by .env.intutic | ✅ .env.intutic | Medium | Microsoft's AutoGen + Semantic Kernel successor (agent-framework/agent-framework-core, imports as agent_framework) — a different framework from AutoGen (row 33), with its own function-middleware chain. On a deny the middleware sets context.result to the [Intutic Governance] BLOCKED: message and returns without call_next(): the tool body never runs, the model receives the refusal as the tool result, and the run continues. A gate error raises MiddlewareFailure, the framework's own fail-closed signal, which aborts the run (an ordinary exception would be turned into a generic tool error and the loop would keep going). MiddlewareTermination was rejected: it ends the function-calling loop before the model sees the result. Verified live against agent-framework-core==1.20.0 by driving a real Agent with a stub chat client. MCP tools are FunctionTools and go through the same middleware (read from source, not driven live). Detected by agent-framework/agent_framework in a Python manifest. See the integration page |
Hook timeouts
Most harnesses run a tool call whose PreToolUse hook outlives its timeout, or fails, as if the hook had allowed it. So a gate that is slow on a crafted call is no gate, and every gate bounds itself:
- A deadline. A hook gate still deciding at its deadline refuses the call with
GATE_DEADLINEin its reason. Each gate's deadline is one second under the hook timeout its harness applies, and at most 9 seconds, the table below. The second covers starting the interpreter and getting the refusal read: on a 14-core machine running every package's test suite beside 100 busy threads, a refusal reached the harness at most 260 ms after the deadline. The bash gates run a detached watchdog that stops the gate'sgreporpython3and signals it; the JavaScript gates run their rules under avmtimeout that interrupts even a regular expression mid-match. The in-process gates (the OpenCode plugin, the Pi extension, the OpenClaw plugin) measure it from the start of each call. - A size limit. A command over 256 KiB, or tool arguments over 1 MiB of compact JSON (UTF-8 bytes), is refused as
COMMAND_TOO_LARGEbefore any rule runs, in the hook gates and both gate SDKs. Across 85,314 tool calls in real coding-agent transcripts, the largest shell command was 38.5 KB, the largest file write 120 KB and the largest arguments of any tool 418 KB. - Linear rules. Every built-in rule runs in time linear in the call: rules shaped
A.*Brun as a sequence of steps (sin a.rulesline's flags), a static check refuses any rule a backtracking engine could take longer on, and every rule carries 256 KiB adversarial cases each gate must decide in linear time. Only a workspace's ownWHEREpattern can be slow, and the deadline covers it.
A legitimate call stays well inside its deadline, even on a busy machine: on the same loaded machine, a call at the size limits took the bash gates (which start python3) at most 4.6 seconds against their 9, and the JavaScript gates at most 1.2 seconds; the 4-second deadlines are all JavaScript gates'.
Where a harness has a setting to refuse on a failed or timed-out hook, connect sets it; where it has a key for the hook's timeout, connect sets 10 seconds.
| Harness | Default timeout | On timeout | What connect sets | Gate deadline |
|---|---|---|---|---|
| Claude Code | 600 s | runs the call | timeout: 10 | 9 s |
| Cursor | not documented | refuses (failClosed) | timeout: 10, failClosed: true | 9 s |
| Windsurf | not documented | not documented; any exit but 2 runs the call | nothing to set | 4 s |
| Codex CLI | 600 s | runs the call | timeout: 10 | 9 s |
| Gemini CLI | 60 s (timeout in ms) | runs the call | timeout: 10000 | 9 s |
| Antigravity | 30 s | not documented | timeout: 10 | 9 s |
| Cline | 30 s, fixed | runs the call | nothing to set | 9 s |
| GitHub Copilot (VS Code agent hooks, CLI, cloud agent) | 30 s | runs the call, admin policy hooks included | timeout: 10 (the CLI's alias for timeoutSec) | 9 s |
| Goose | 30 s | runs the call; refuses with on_failure: "block" | on_failure: "block", timeout: 10 | 9 s |
| OpenHands | 60 s | runs the call | timeout: 10 | 9 s |
| Hermes (shell hooks) | 60 s | runs the call; refuses with fail_closed: true | fail_closed: true, timeout: 10 | 9 s |
| Grok Build | 5 s | runs the call | timeout: 10 | 9 s |
| Muse Code | not documented | not documented | nothing to set | 4 s |
| OpenClaw (plugin hook) | 15 s | refuses (the hook runner fails closed) | timeoutMs: 10000 on the hook registration | 9 s |
| Pi (extension) | none (in process) | waits | nothing to set | 9 s |
| OpenCode (plugin) | none (in process) | waits | nothing to set | 9 s |
| n8n, Open WebUI, dsh | none (in process) | waits | nothing to set | none |
Windsurf and Muse Code document no hook timeout, so their gates assume the shortest default of any harness that runs a timed-out call, Grok Build's 5 seconds.
Per-Harness Onboarding Guide
Every harness follows the same steps: intutic init detects the harness and records it in ~/.intutic/config.json, and intutic gates install writes the files listed below for each recorded harness: its gate always, whether or not any SOP targets it, its proxy routing with --route, and its rules file when at least one SOP targets it (any local SOP in .intutic/sops/). To add a harness intutic init did not detect, add its id to the harnesses list in that file. See What writes harness files. Files you also edit are merged, never replaced; one that does not parse is left untouched and reported. Where a row says MCP servers are wrapped, see How a server gets here at all.
| Harness | What intutic gates install writes |
|---|---|
Claude Code (claude-code) | .claude/rules/intutic-governance.md (not CLAUDE.md); permissions.deny and PreToolUse hooks merged into .claude/settings.json and ~/.claude/settings.json; gate .intutic/hooks/claude-code-check.js; MCP servers in ~/.claude.json wrapped. |
Cursor (cursor) | .cursor/rules/intutic-governance.mdc (alwaysApply: true); hooks merged into .cursor/hooks.json and ~/.cursor/hooks.json; gate .intutic/hooks/cursor-check.js; MCP servers in .cursor/mcp.json and Cursor's global settings wrapped. |
Windsurf (windsurf) | .windsurf/rules/intutic-governance.md (trigger: always_on); hooks merged into ~/.codeium/windsurf/hooks.json, ~/.codeium/hooks.json and .windsurf/hooks.json; http.proxy/codeium.proxy merged into Windsurf's user settings.json, pointing at the proxy's port (the port of INTUTIC_PROXY_URL, 4000 by default), which serves TLS interception on the same listener; the proxy's CA added to the trusted roots on macOS and Windows, and on Linux when connect runs as root (otherwise it prints the sudo commands); the IDE proxy of JetBrains IDEs where the Windsurf plugin is set up. See the Windsurf page. |
Cline (cline) | .clinerules/intutic-governance.md; the file hook .clinerules/hooks/PreToolUse. Route the LLM through the proxy in Cline's own settings. |
Roo Code (roo-code) | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers (not .roorules). No hook system; route the LLM in Roo Code's own settings. |
Aider (aider) | Merged into .aider.conf.yml: openai-api-base, an ANTHROPIC_BASE_URL set-env entry and a read entry for .intutic/aider-sops.md by absolute path; test-cmd, lint-cmd, auto-test and auto-lint removed (Aider runs them unprompted). |
OpenHands (openhands) | .openhands/microagents/intutic-governance.md, a microagent with no triggers, which OpenHands keeps active; [llm] base_url merged into config.toml; .openhands/hooks.json PreToolUse hook (fail-closed). |
Goose (goose) | A marked section of the workspace's .goosehints; ~/.agents/plugins/intutic-governance/plugin.json, hooks/hooks.json (on_failure: "block") + scripts/intutic-check.sh, made immutable (chmod 444 plus chflags uchg on macOS or chattr +i on Linux); OPENAI_HOST and ANTHROPIC_HOST merged into ~/.config/goose/config.yaml. |
Codex (codex) | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers; .env.intutic; openai_base_url merged into ~/.codex/config.toml; PreToolUse hook merged into ~/.codex/hooks.json and .codex/hooks.json; gate .intutic/hooks/codex-check.js. |
GitHub Copilot (github-copilot) | A marked section of .github/copilot-instructions.md; agent-mode hook definitions .github/hooks/intutic-governance.json and ~/.copilot/hooks/intutic-governance.json (Preview). |
Continue (continue) | .continue/rules/intutic-governance.md (alwaysApply: true); apiBase on OpenAI/Anthropic models in ~/.continue/config.yaml. No hook: cn never fires one. |
Antigravity (antigravity) | A marked section of the project's GEMINI.md, which Gemini CLI and Antigravity both load; the Antigravity PreToolUse hook under the intutic-governance key of ~/.gemini/config/hooks.json; the Gemini CLI BeforeTool hook merged into ~/.gemini/settings.json. |
Muse Code (muse-code) | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers (Muse falls back to CLAUDE.md if there is no AGENTS.md); the PreToolUse/PermissionRequest gate in .muse/hooks.json and in an Intutic-owned ~/.config/muse/intutic-managed-hooks.json referenced from ~/.config/muse/settings.json (managed_hooks_path); MCP servers under mcp_servers wrapped. The muse binary could not be installed to live-verify the hook contract. |
Grok Build (grok) | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers; PreToolUse hook in .grok/hooks/intutic-governance.json and ~/.grok/hooks/intutic-governance.json; base_url merged into each [model.*] table of the project and user config.toml; MCP servers under [mcp_servers.*] wrapped. Grok Build also runs .claude/settings.json and .cursor/hooks.json hooks if present, so a project governed for Claude Code or Cursor is double-gated — expected. |
OpenCode (opencode) | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers; the in-process plugin .opencode/plugins/intutic-governance.js (1.x) and .opencode/plugins/intutic-governance/index.js (2.x); the mcp block of opencode.json and the global OpenCode config wrapped. Route model traffic yourself with provider.<id>.options.baseURL. |
dsh (dsh) Preview | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers; an intutic-governance plugin row in every existing $DSH_HOME/profiles/*/cordis.patch.yml, @intutic/gate in that profile's package.json, and, with --route, the llm-deepseek route pointed at the proxy. One manual dsh plugin add per profile remains — see the dsh page. |
Hermes (hermes) | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers; a hooks.pre_tool_call entry (fail_closed: true) merged into ~/.hermes/config.yaml; gate ~/.intutic/hooks/hermes-check.sh; skill ~/.hermes/skills/intutic-governance/SKILL.md. |
Pi (pi) | A marked section of the workspace's AGENTS.md, shared with the other AGENTS.md readers; the extension ~/.pi/agent/extensions/intutic-governance.js; anthropic/openai provider baseUrl merged into ~/.pi/agent/models.json. |
n8n (n8n) | No instructions file; the external-hook gate ~/.intutic/hooks/n8n-governance-hook.js and ~/.intutic/n8n/INSTALL.md — the n8n operator sets EXTERNAL_HOOK_FILES once. |
OpenClaw (openclaw) | A marked section of AGENTS.md in OpenClaw's own agent workspace (agents.defaults.workspace, else OPENCLAW_WORKSPACE_DIR, else ~/.openclaw/workspace); the plugin ~/.intutic/hooks/openclaw/intutic-governance.cjs, listed in plugins.load.paths (and plugins.allow, when that list is not empty) in ~/.openclaw/openclaw.json. |
Open WebUI (open-webui) | ~/.open-webui/intutic-governance-filter.py, which an Open WebUI admin adds as a Function once. Not auto-detected: add open-webui to harnesses. |
Claude Desktop (claude-desktop) | Nothing besides the MCP wrapping of claude_desktop_config.json and the drift guard on that file. |
| SDK-gated frameworks (LangGraph, LangChain, CrewAI, AutoGen, AG2, Google ADK, OpenAI Agents SDK, Pydantic AI, smolagents, Strands Agents, Microsoft Agent Framework, Mastra, Vercel AI SDK, eve, AI SDK Harness, AI SDK Workflow, TrueForge) | .env.intutic with ANTHROPIC_BASE_URL (proxy host), OPENAI_BASE_URL (proxy host + /v1) and a pointer at the SDK-side gate you install in your code — see each framework's page. |
| Xirp, Agentic Orchestrator, AWS Bedrock AgentCore Runtime | Nothing — they run other harnesses (or your framework code), each governed by its own row. |
TrueForge Server (trueforge-server) | Nothing — governed by the TrueForge bridge service. See Server mode. |
Known Architectural Gaps (Permanent)
| Harness | Gap | Reason | Mitigation |
|---|---|---|---|
| Cursor-hosted models | No proxy interception | Requests for Cursor's own models are sent from Cursor's backend, not from the editor | Hooks still gate shell commands, MCP calls and file writes; requests made with your own API keys can be routed through the OpenAI base-URL override |
| Claude Desktop | No LLM proxy interception, no hook system | Locked to Anthropic; no base-URL override; no documented pre-tool hook | MCP servers wrapped by the MCP governance proxy + drift guard watches config for rogue MCP servers |
| Windsurf Cascade AI | Cannot intercept without TLS MITM | Windsurf has no base-URL setting for Cascade's models | TLS MITM via the proxy's local CA — see the Windsurf page |
| n8n granularity & install | Workflow-level, manual install | EXTERNAL_HOOK_FILES is read by the n8n server process at startup — the daemon cannot set another process's environment, and workflow.preExecute gates whole executions, not individual tool calls | n8n-governance-hook.js + INSTALL.md generated every sync; one offending node aborts the execution with an error naming the node and rule |
| GitHub Copilot hook stability | Agent hooks are Preview | The .github/hooks/*.json format may change between VS Code releases | The gate refuses stdin payloads it does not recognise (fail closed); instructions file remains as a fallback layer |
| Muse Code proxy routing | No persistent proxy base-URL setting confirmed in settings.json | Muse exposes a --base-url CLI flag and a META_API_KEY env var, but no equivalent persistent config key could be confirmed from available docs/source (beta product) | Document-only for now: route via the launcher flag or env var; the client hook (PreToolUse/PermissionRequest, both tiers) remains the primary enforcement surface regardless of egress routing |
| Strands Agents' default (Bedrock) LLM egress | No proxy interception (Vectors B and D) for SigV4-signed providers | Strands Agents defaults to BedrockModel — boto3 calls SigV4-signed against bedrock-runtime.<region>.amazonaws.com. The Intutic proxy does not speak Bedrock's wire protocol and cannot re-sign SigV4 requests, so no base-URL env var can route this traffic through it (same applies to Strands' SageMaker provider). First framework in the SDK-gated family whose DEFAULT egress is unroutable | The SDK tool gate (intutic_clawde.gate.adapters.strands, BeforeToolCallEvent.cancel_tool — MCP tools included) is the enforcement point regardless of provider; Anthropic/OpenAI/LiteLLM providers remain proxy-routable via .env.intutic's base-URL vars. See the integration page |
Known Non-Integratable Platforms
Internal-only or otherwise closed AI agent platforms this product cannot integrate with today, because no public artifact exists for Intutic to detect, gate, or route traffic through — not a decision to skip them, a hard blocker. Listed here (rather than silently omitted) so an audit does not mistake absence for an oversight, and re-checked periodically for a public release. Distinct from Known Architectural Gaps above, which covers harnesses this product DOES integrate with but cannot fully cover — these rows cover harnesses with zero integration surface, full stop.
| Platform | Why it can't be integrated | Evidence | Monitoring |
|---|---|---|---|
| DoorDash Flux | Real product, confirmed to exist — but internal-only by design, not merely undiscovered. DoorDash's own engineering blog and a DoorDash executive's own public statement both describe it explicitly as an internal cloud-based agents platform (background coding/review/triage agents in isolated sandboxes, triggered from Slack/GitHub/CLI, governed via an internal MCP gateway) — there is no installable client, no config file, no binary, and no API surface exposed outside DoorDash's own infrastructure for Intutic to detect or gate | careersatdoordash.com engineering blog describes Flux as DoorDash's internal cloud-based agents platform; a DoorDash co-founder's own public post calls it "Our internal platform Flux"; no doordash-oss (or any other public DoorDash) GitHub repo, npm/PyPI package, or Homebrew formula named Flux was found — the same org that publishes Agentic Orchestrator publicly has published nothing for Flux | Re-check on future harness-onboarding waves; graduates to a real integration the day a public artifact (repo, package, or binary) exists |