Read/Write Governance
Read/write governance lets a project decide which governed agent actions may proceed, which require human review, and which are denied. The control is a project policy over Keel-derived action facts: access level, risk tags, connector identity, classifier source, and registry version.
For governed routes, Keel records those facts on the permit/audit metadata. On supported plans, decision snapshots, signed exports, and Keel’s tamper-evident integrity stack can then show what Keel classified, which policy ran, and what decision Keel returned for the actions that routed through Keel. That is the proof boundary; it is not a claim that Keel governs every action an agent can take off path.
What Keel governs
Keel governs actions that route through a Keel decision boundary:
| Surface | What Keel can govern | Boundary |
|---|---|---|
| Model calls through Keel execution routes | Policy and budget before provider dispatch | Model operations are usually read on the access ladder, but billable calls can also carry the spend risk tag. |
| MCP tools routed through Keel | Exact tool calls after Keel resolves a verified connector identity and tool name | Exact mapped names classify as read, write, or admin; unmapped names classify as unknown. |
| Payments routed through Keel | Spend-bearing payment actions | Payment and positive-cost actions carry spend. |
| Code execution routed through Keel | Code-execution operations and known code-exec tools | Classified as admin with code_exec. |
| Credential-scoped databases | Database access through Keel-verified read-only or read-write credentials | The downstream credential scope is the control; Keel does not parse SQL text to decide read versus write. |
Keel does not govern direct calls the agent makes with its own provider, API, browser, database, or tool credentials. Browser and computer-use actions are coarse unless a concrete action is mediated by a governed Keel route or tool. On permit-first integrations, Keel records the decision before execution, but the caller still owns the downstream call and must honor the decision.
What Keel classifies
When action access is enabled for a project, governed policy context can include:
| Field | Meaning |
|---|---|
context._keel.action_access_level | One of none, read, write, admin, or unknown. |
context._keel.action_risk_tags | Independent risk tags: spend, egress_external, data_export, and code_exec. |
context._keel.action_access_source | Where the classification came from, such as keel_curated_connector_map, keel_trusted_credential_scope, operation_intrinsic, or none. |
context._keel.action_access_confidence | Classification confidence. A read classification requires high confidence. |
context._keel.action_access_map_version | The classifier registry version used for mapped connector or credential-scope classifications. The current live registry verified for this page is 2026-07-02.1. |
context._keel.action_access_registry_hash | Hash of the active classifier registry manifest. |
context._keel.action_access_unknown_reason | Why Keel could not classify the action, when the level is unknown. |
context._keel.connector_identity | The Keel-verified connector identity. Keel does not trust a customer-renamable MCP server slug for read/write gates. |
context._keel.intent_mismatch | Tripwire only: true when the caller declared less access than Keel derived, or Keel could not confirm the declared access. |
Caller-supplied context._keel values are stripped before evaluation. A caller
can declare intent, but declared intent never grants access.
Access levels
Access level is one ordered ladder. unknown is not a rung; it is the
fail-closed sentinel.
| Level | Meaning | Examples in the live registry |
|---|---|---|
none | No action access beyond the permit decision itself. | Used as an allowable read-only level; most concrete tools classify as read, write, admin, or unknown. |
read | Retrieves existing state without mutating the downstream system. | GitHub get_file_contents; database query through postgres.readonly; Stripe get_payment_intent; filesystem read_text_file; Slack conversations.info. |
write | Mutates content, resource state, workflow state, or money movement. | GitHub create_issue; database query through postgres.readwrite; Stripe create_payment; filesystem write_file; Slack chat.postMessage. |
admin | Changes privileged control state or runs a high-authority operation. | GitHub merge_pull_request or create_repository; Google Drive share_file; S3 PutBucketPolicy; Slack conversations.invite; code execution run_command. The current registry does not expose an admin bucket for credential-scoped database or Stripe tools. |
unknown | Keel did not classify the action at high confidence. | Unmapped MCP tools, unverified connector identities, unscoped database query, and generic common-MCP names such as read, list, get, or search. |
Risk tags
Risk tags are independent of access level. A read-only policy should check both the ladder and the tag set.
| Tag | Meaning | Examples in the live registry |
|---|---|---|
spend | The action spends, reserves, refunds, transfers, or can incur billable cost. | Stripe create_payment, stripe_api_write, create_refund; payment action payment.execute; model calls with positive estimated cost. |
egress_external | The action sends data outside the expected trust boundary. | Slack chat.postMessage; GitHub write/admin tools such as create_issue; Google Drive share_file; Stripe send_stripe_mcp_feedback. |
data_export | The action exports or exposes data as an explicit export/share/report operation. | Google Drive share_file; S3 bucket-policy/public-access admin tools; Stripe stripe_report. |
code_exec | The action executes code. | Operation code_execution; code-execution tools such as run_command, bash, python, and node. |
Fail-closed behavior
Read/write governance is intentionally conservative:
- unmapped or unknown tools classify as
unknown unknownis not read-only safe and should review or deny- policy authoring rejects active policies that allow unclassified actions
- low- or medium-confidence classifications do not become
read - generic MCP verb names such as
read,list,get, orsearchare not trusted as read semantics by themselves - database read/write classification comes from the credential scope Keel provisioned or verified, not from SQL parsing
- any risk tag, including a future tag, blocks read-only-safe classification
The default for unknown and unmapped tools is review. Projects can write stricter policies that deny them instead.
Enable read-only mode for a project
For eligible hosted projects, read/write controls are generally available. They
do not change behavior until the project has an active policy that uses
context._keel.action_access_* fields.
In the dashboard, create or edit a project policy in Guided or Technical mode. Choose the read-only access mode, or author an equivalent policy. Save it as the active project policy.
Read-only means:
- allow
action_access_levelvaluesnoneandread - review or deny
write,admin, andunknown - review or deny any non-empty
action_risk_tagslist
A read-only review policy has this shape:
{
"name": "Read-only guardrail",
"rules": [
{
"if": {
"any": [
{
"field": "context._keel.action_access_level",
"op": "in",
"value": ["write", "admin", "unknown"]
},
{
"field": "context._keel.action_risk_tags",
"op": "len_gt",
"value": 0
}
]
},
"action": "require_human_review"
}
]
}Use deny instead of require_human_review when the project should block
matching actions outright.
If read/write fields are not enabled for a project, Keel rejects active policies
that reference them. A policy that can match unknown cannot use allow; for a
read-only policy, use require_human_review or deny.
Coverage boundaries
Keel’s strongest read/write coverage is on surfaces where Keel is on the execution path or verifies the downstream credential scope.
| Surface | Boundary |
|---|---|
| GitHub | Deepest connector-map coverage today. Registry 2026-07-02.1 maps audited GitHub MCP tool names exactly; future or absent names remain unknown. |
| Database | Credential-scoped, not SQL-parsed. postgres.readonly/database.readonly classify as read; postgres.readwrite/database.readwrite classify as write; unscoped database identities are unknown. |
| Stripe and payments | Retrieve/list tools classify as read; create/update/payment/refund/transfer tools classify as write and usually carry spend; mixed export/report tools are not treated as read. |
| Files and storage | Filesystem, Google Drive, and S3 use exact-name maps. Generic file-like names under the wrong connector are intentionally unmapped. |
| Slack, Gmail, email | Slack and Gmail have exact-name read/write/admin coverage. Generic email has no read-classified tools until a connector-specific contract pins exact non-mutating semantics. |
| Common MCP | Generic names alone do not prove semantics. read, list, get, and search are unknown, not read. |
| Browser or computer-use | Coarse. computer.use is ambiguous from the operation alone and rounds up; off-path browser activity is not governed. |
Registry coverage is versioned and audited. The policy-authoring catalog reports project MCP coverage from the project’s configured tool allowlists, including the number of unknown tools. Missing entries are safer than wrong read entries: an unmapped tool reviews by default, while a mutating tool misclassified as read could fail open.