Named reference journeys (GitHub, files/storage, email, Slack, customer data,
cloud and database) have evidence only at their recorded scope; live vendor
execution is not certified. This does not change enforcement of exact requests
through enrolled managed MCP :call. See Scope and Limits.
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 managed MCP :call tool calls after Keel resolves a verified connector identity and tool name | Only connector identities Keel has enrolled (an operator step) can reach a policy allow. Exact mapped names classify as read, write, or admin; unmapped names classify as unknown and are refused. A server you register yourself is held for review on every call; a human-approved action mapping can classify its tools, but their calls stay review-only. |
| 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, code_exec, refund, pii_access, and value_movement. |
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. Read the version from the decision record; this page does not pin one. |
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. |
value_movement | The action moves money or other value. | Payment action payment.execute. |
refund | The action returns money to a payer. | — |
pii_access | The action reads or writes personal data. | — |
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
Keel refuses unknown and unmapped tools at its platform safety floor
(platform_safety_floor.unknown_action). No project policy can allow them.
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, Guided Read only adds a restriction rule. It does not
create positive authority: add an independently bounded positive grant where
appropriate, or use Technical mode to author the explicit policy below. Save
the policy as the active project policy. This requires Production or Enterprise: Starter’s Basic
authoring cannot use allow, the exists operator, or the
context._keel.action_access_* fields, so Starter cannot express a read-only
policy.
Read-only means:
- explicitly allow
action_access_levelvaluesnoneandread— Keel executes only what a matchingallowrule authorizes, so a policy with noallowrule denies every request, reads included - deny
writeandadmin - give no positive grant to
unknown, missing classification, or non-emptyaction_risk_tags; restriction rules may additionally record REVIEW
For harmless classified reads, this Technical-mode example supplies explicit positive authority. It is not the output of the Guided read-only compiler:
{
"name": "Action access: read only",
"rules": [
{"if": {"not": {"field": "context._keel.action_access_level", "op": "exists", "value": true}}, "action": "require_human_review"},
{"if": {"not": {"field": "context._keel.action_risk_tags", "op": "exists", "value": true}}, "action": "require_human_review"},
{
"if": {
"any": [
{"field": "context._keel.action_risk_tags", "op": "contains", "value": "code_exec"},
{"field": "context._keel.action_risk_tags", "op": "contains", "value": "data_export"},
{"field": "context._keel.action_risk_tags", "op": "contains", "value": "egress_external"},
{"field": "context._keel.action_risk_tags", "op": "contains", "value": "pii_access"},
{"field": "context._keel.action_risk_tags", "op": "contains", "value": "refund"},
{"field": "context._keel.action_risk_tags", "op": "contains", "value": "spend"},
{"field": "context._keel.action_risk_tags", "op": "contains", "value": "value_movement"}
]
},
"action": "require_human_review"
},
{"if": {"field": "context._keel.action_access_level", "op": "eq", "value": "unknown"}, "action": "require_human_review"},
{"if": {"field": "context._keel.action_access_level", "op": "in", "value": ["write", "admin"]}, "action": "deny"},
{"if": {"all": [{"field": "context._keel.action_access_level", "op": "in", "value": ["none", "read"]}, {"field": "context._keel.action_risk_tags", "op": "eq", "value": []}]}, "action": "allow"}
]
}This grant requires both a classified harmless read and an explicitly empty
risk-tag list. Missing, null, malformed, or non-empty tags do not satisfy it.
Risk-tagged and unknown requests have no grant in this example; approval cannot
release them. The platform safety floor also refuses unknown actions. If a
project needs a reviewable consequential action, give that action its own
bounded grant with all required trusted facts. Use deny instead of REVIEW
when the restriction should record outright refusal.
If read/write fields are not enabled for a project, Keel rejects active policies
that reference them. An allow rule whose condition can match unknown is a
policy validation error (unclassified_action_cannot_allow); rules that match
unknown must review, deny, or throttle.
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 | The curated registry maps audited GitHub MCP tool names exactly; future or absent names remain unknown. This is classification coverage, not live GitHub journey certification. The github connector identity is enrolled by Keel, not by the customer. |
| 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 is refused, while a mutating tool misclassified as read could fail open.