Skip to content

RFC: canonical Cedar schema library for agent action verbs (exec, open, connect, request_tool) #76

@tomjwxf

Description

@tomjwxf

Summary

Proposing a canonical Cedar schema library for the four universal AI-agent action verbs: exec, open, connect, request_tool. The goal is a single schema file that every Cedar-based agent-governance system (AGT, sb-runtime, protect-mcp, bindu-scopeblind, Signet, nono policy bridges, and future implementers) can import as the shared vocabulary for agent-action policy authoring.

Opening this as a discussion issue (not a PR) so the design is agreed before code lands. Happy to be the one to ship the schema PR once the shape is settled; not asking anyone else to own the work.

Why this matters

Today every agent-governance stack defines its own Cedar schema for the same four things:

  • exec / shell:<cmd> / command / process.spawn / subprocess (shell command execution)
  • open / file:read:<path> / fs.read / filesystem.open (file read/write)
  • connect / http:GET:<url> / net.request / network.fetch (network socket or HTTP)
  • request_tool / tool:<name> / mcp:call / agent_call (MCP or agent-SDK tool invocation)

Operators who compose multiple stacks — which is increasingly the norm (e.g. AGT + sb-runtime + protect-mcp + nono) — end up writing the same policies in four different dialects.

A canonical schema library in cedar-for-agents closes that gap. Every governance system downstream consumes the same namespace for the same primitive. Operator policies become portable.

Scope

This issue proposes adding a cedar-policy-agent-action-schema crate to the workspace with:

  1. A Cedar schema file (.cedarschema.json) defining four entities:

    • Agent::Action::exec with command: String, argv: Set<String>, cwd: String, uid: Long
    • Agent::Action::open with path: String, mode: String, size_bytes: Long
    • Agent::Action::connect with host: String, port: Long, protocol: String, tls: Boolean, method: String?, url: String?
    • Agent::Action::request_tool with tool_name: String, tool_server: String?, args_hash: String, transport: String
  2. Reference principals:

    • Agent::Principal::Agent with agent_id: String, trust_score: Decimal, ring: Long, session_id: String?
  3. Reference resources:

    • Agent::Resource::File, Agent::Resource::Endpoint, Agent::Resource::Tool
  4. A policy-set library of 10-15 common patterns (allow-read-workspace.cedar, deny-credential-exfiltration.cedar, rate-limit-tool-calls.cedar, etc.) that downstream systems can import verbatim or compose.

  5. TypeScript + Python bindings that produce Cedar requests from typed inputs. Rust is covered by existing cedar-policy crates.

What I'd propose it looks like (strawman)

Schema

{
  "Agent": {
    "entityTypes": {
      "Principal": { "shape": { "type": "Record", "attributes": {
        "agent_id": { "type": "String" },
        "trust_score": { "type": "Decimal" },
        "ring": { "type": "Long" },
        "session_id": { "type": "String", "required": false }
      }}},
      "File":     { "shape": { "type": "Record", "attributes": {
        "path": { "type": "String" },
        "owner_uid": { "type": "Long", "required": false }
      }}},
      "Endpoint": { "shape": { "type": "Record", "attributes": {
        "host": { "type": "String" },
        "port": { "type": "Long" }
      }}},
      "Tool":     { "shape": { "type": "Record", "attributes": {
        "name": { "type": "String" },
        "server": { "type": "String", "required": false }
      }}}
    },
    "actions": {
      "exec":         { "appliesTo": { "principalTypes": ["Principal"], "resourceTypes": [] }},
      "open":         { "appliesTo": { "principalTypes": ["Principal"], "resourceTypes": ["File"] }},
      "connect":      { "appliesTo": { "principalTypes": ["Principal"], "resourceTypes": ["Endpoint"] }},
      "request_tool": { "appliesTo": { "principalTypes": ["Principal"], "resourceTypes": ["Tool"] }}
    }
  }
}

Example policy using it

// Allow read-only access inside /workspace for any agent at ring >= 2
permit (
    principal is Agent::Principal,
    action == Agent::Action::"open",
    resource is Agent::File
) when {
    principal.ring >= 2 &&
    resource.path like "/workspace/*" &&
    context.mode == "read"
};

// Deny network to cloud metadata regardless of trust
forbid (
    principal,
    action == Agent::Action::"connect",
    resource is Agent::Endpoint
) when {
    resource.host == "169.254.169.254" ||
    resource.host == "metadata.google.internal"
};

Alignment with merged work

This schema library would be the natural foundation for:

  • The RequestGenerator WASM bindings (feat: RequestGenerator WASM bindings for Cedar authorization requests #73) — once that merges, cedar-for-agents will have a clean surface for producing authorization requests from TypeScript. A shared schema means every consumer produces requests against the same entity types, making policies portable across WASM bindings, the existing Rust generator, and bindings in other languages.
  • SchemaGenerator (feat: WASM bindings for cedar-policy-mcp-schema-generator #64, merged) — SchemaGenerator already produces Cedar schemas from MCP tool descriptions. A canonical base schema for the four universal action verbs would be the skeleton those tool-specific extensions layer onto, rather than each schema starting from scratch.
  • Agent-governance downstream systems — Microsoft Agent Governance Toolkit (3 PRs merged), protect-mcp, sb-runtime, Signet (Prismer-AI), hermes-decision-receipts (aeoess), bindu-scopeblind, nono, and the 15 implementations currently named in draft-farley-acta-signed-receipts-02 Implementation Status.

Open questions for discussion

  1. Namespace. Agent::* or something more specific (AgentOps::*, CedarAgents::*)? The simplest choice (Agent) is short and memorable but might collide with other domains.
  2. Should this be a separate crate or a module in mcp-tools-sdk? I'd lean separate crate (cedar-policy-agent-action-schema) so it's usable without the MCP machinery.
  3. Versioning. Schema changes across minor versions should not break existing policies. Want to mirror the IETF draft's backward-compat posture (additive-only optional fields between minor revisions).
  4. Relationship to OWASP Agentic Top 10. Each action verb has a natural mapping (exec → OAT-05 Tool Misuse, connect → OAT-02 Privilege Escalation, etc.). Worth documenting the mapping in the schema README?

Willing to ship

I'm the author of the IETF draft-farley-acta-signed-receipts receipt format and the open-source @veritasacta/verify reference verifier. I'd happily open the schema PR once the design is agreed here. Not requesting anyone else do the work.

No rush on response; know that #73 is actively in review and this is secondary work. If the framing is off or the scope is too wide, happy to narrow or split.

Cc: whoever's the right person on the AWS Cedar team for this (no pings intended).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions