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. |
The Stripe row describes action classification, not blanket execution coverage.
Keel does not expose a first-class general Stripe refund rail. The exact
stripe.refund.create action is Keel-enforced only when it uses an enrolled
managed MCP :call contract. That beta/limited path has been demonstrated with
genuine Stripe TEST refunds; the customer adapter owns the final Stripe HTTP
call and TEST credential, live mode is not established, and direct Stripe calls
with separate credentials bypass Keel.
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.