Event types: how a tool call is judged

What Prismor turns each tool call into before any rule runs, which rules each type gets, and what "Unclassified" means on the console. Companion to docs/enterprise-tool-access.md and docs/tool-tags.md.


One event shape for every agent

Claude Code calls its shell tool Bash. Codex calls it exec_command, Cursor calls it run_terminal_cmd, and a LangChain agent might call it terminal. Each one sends its arguments in a different shape. Prismor maps every call to one event type and copies the value that matters into one field:

Event typeConsole labelField rules readTypical tools
shellShell commandcommandBash, PowerShell, exec_command, run_terminal_cmd, SDK tools with a command/cmd/script/code argument
file_readFile readpathRead, read_file, view, an upload (it reads a local file)
file_writeFile writepath (+ content)Edit, Write, apply_patch, write_file
networkNetworkurl (+ outbound_payload)WebFetch, fetch, browser navigate, a remote MCP server
subagent_spawnSubagentcontentAgent/Task, OpenAI handoffs, CrewAI delegation
tool_resultUnclassifiedresponseLocal (stdio) MCP tools, skills, any tool Prismor can't place
prompt—promptThe user's prompt

Rules are written against the type, not the tool name. destructive-command says "on shell, look at command", so it covers every agent above without listing their tool names. That is why one policy applies across all agents.

"Unclassified" (tool_result)

A call is Unclassified when the tool name and its arguments don't say what it does. Its arguments are serialized to JSON and put in response.

It is not skipped. It still gets:

  • Tool access by name. The Allowed/Denied toggle and tool_denies match the exact tool name (mcp__github__create_issue, Skill:wiki-query, Monitor).

  • The content rules. These read text and match any type:

    RuleLooks for
    prompt-injectioninstruction override, system-prompt extraction, covert exfiltration
    prompt-injection-hiddeninjection hidden in HTML comments or invisible elements
    bulk-pii-exfiltrationrequests to export whole datasets of personal data
    pii-exposureSSNs, card numbers, US phone numbers
    model-manipulationoverriding model parameters, tampering with tool definitions
    skill-self-persisttext telling the agent to persist and auto-update from a URL
  • Everything that isn't type-specific: tag rules (tool-tags.md), rules with the mcp event type, the LLM judge when enabled, and cloak's secret scan.

What it does not get is the action rules: every rule on shell, file_read, file_write and network, which is most of the policy. Those rules read command, path or url, and an Unclassified call leaves them empty.

So a local MCP tool called run with {"cmd": "rm -rf ~"} is allowed unless its name is denied or its text trips a content rule. destructive-command never sees it.

Unclassified is not an error. For a tool that only returns data, it is the right type. It is a gap when the tool runs commands, touches files or calls URLs.

Correcting a type ("Judged as")

On the console, open an agent's Tool access panel. Each tool shows Judged as with the type its latest call got:

  • (as reported) is what the runtime decided.
  • Amber means an admin corrected it. "was X" shows the type the runtime decided.
  • Rose means Unclassified.
  • A correction set at a broader scope shows as the blank option, e.g. "Network (org-wide)" on an agent page or "File read (this agent)" on a session page. Picking a type overrides it for this agent or session only.

When any tool is Unclassified, the top of the panel says how many, and Show only these filters the list to them.

The policy editor lists corrections under Type corrections, separately from blocked and force-allowed tools.

Picking a type ships a signed classify entry in settings.tool_denies. Before policy runs, the runtime retypes that tool's calls:

  • It fills the new type's field from the call's arguments:
    • command/cmd/script/code → command
    • path/file_path/file_name/… → path
    • a URL argument → url
  • The original text moves to content, so the content rules still see it.
  • The event records metadata.type_override (from, to, tool, scope) for the audit trail.

The narrowest scope wins: session, then agent, then device, then org. Choosing the blank option removes this scope's correction. Runtimes older than the release that added classify ignore the entry.

The same entry over the API:

POST /api/admin/tool-policy
{"orgId":"<org>","tool":"mcp__acme__run","action":"classify","type":"shell","scope":"agent","scopeId":"checkout-bot"}

Finding out for yourself

The rules that apply to a type, with titles:

prismor policy export | jq -r '.rules[] | select(.event_types|index("tool_result")) | "\(.id)\t\(.title)"'

How many rules each type gets:

prismor policy export | jq -r '[.rules[].event_types[]] | group_by(.) | map("\(.[0]) \(length)") | .[]'

What type your recent calls got:

prismor query "SELECT ts, type, coalesce(json_extract(raw_json,'$.tool_name'), json_extract(raw_json,'$.metadata.tool_name'), json_extract(raw_json,'$.mcp_tool')) AS tool FROM events ORDER BY ts DESC LIMIT 20"