Skip to content
ARP / SPEC
VERSION v0.1 — DRAFT

Policies & Cedar

Purpose: show exactly how a Connection's policy is authored, compiled, and evaluated, using the scenario Ian (owns samantha.agent) + Nick (owns ghost.agent) collaborating on Project Alpha.


1. The three layers of a policy

┌──────────────────────────────────────────────────┐
│ Layer 1 — Scope Template (human UX)              │
│ "Let Ghost read Project Alpha files during       │
│  business hours, up to $50 in paid queries."    │
├──────────────────────────────────────────────────┤
│ Layer 2 — Cedar Policy (wire format)             │
│ permit (...) when {...}                          │
├──────────────────────────────────────────────────┤
│ Layer 3 — UCAN/Biscuit Envelope (signed)         │
│ { iss, sub, aud, cedar_policies[], sigs, ... }   │
└──────────────────────────────────────────────────┘

Owner sees Layer 1. Agents exchange Layer 3. The PDP evaluates Layer 2.


2. Scenario

  • Ian owns samantha.agent (his personal agent)
  • Nick owns ghost.agent (his personal agent)
  • They're collaborating on Project Alpha — a research project whose files live in Samantha's memory
  • Ian wants Ghost to be able to read the Alpha project files and discuss scheduling, but nothing else
  • Ian wants the connection to cap at $50/month in paid operations
  • Ian wants business-hours-only access
  • Ian wants all mentions of specific client names redacted on the way out

3. Example 1 — Minimal policy (starter)

Layer 1 — what Ian picks in the UI

[ New Connection: ghost.agent ]

Purpose:  Project Alpha

Permissions:
  ☑ Read project files        Project: [Alpha ▾]
  ☐ Write project files
  ☐ Read calendar
  ☐ Share externally

Layer 2 — compiled Cedar

permit (
    principal == Agent::"did:web:ghost.agent",
    action in [Action::"read", Action::"list"],
    resource in Project::"alpha"
);

Layer 3 — signed envelope (excerpt)

{
  "connection_id": "conn_7a3f...",
  "issuer":   "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
  "subject":  "did:web:samantha.agent",
  "audience": "did:web:ghost.agent",
  "purpose":  "project:alpha",
  "cedar_policies": [
    "permit (principal == Agent::\"did:web:ghost.agent\", action in [Action::\"read\", Action::\"list\"], resource in Project::\"alpha\");"
  ],
  "expires": "2026-10-22T00:00:00Z",
  "sigs": { "ian": "...", "nick": "..." }
}

4. Example 2 — Scoped policy (realistic)

Adds time windows, stated purpose, required VCs, spend caps, and a deny rule for sensitive tags.

Layer 1 — UI expansion

[ Edit Connection: ghost.agent · Project Alpha ]

Permissions:
  ☑ Read project files                  Project: [Alpha ▾]
  ☑ Discuss scheduling                  Days ahead: [14]
  ☑ Summarize documents                 Max output: [2000 words]

Access windows:
  ☑ Business hours only                 09:00–17:00 [America/New_York ▾]
  ☑ Weekdays only

Counterparty requirements (attribute VCs):
  ☑ Verified human
  ☑ Over 18
  ☐ US resident
  ☐ Specific employer

Economic limits:
  ☑ Spend cap: $50 / rolling 30 days
  ☑ Per-request cap: $5

Sensitive data:
  ☑ Never share items tagged "confidential"
  ☑ Never share items tagged "client-list"

Expires: [2026-10-22]

Layer 2 — compiled Cedar

// --- PERMIT rules ---
permit (
    principal == Agent::"did:web:ghost.agent",
    action in [Action::"read", Action::"list", Action::"summarize"],
    resource in Project::"alpha"
) when {
    // Business hours, weekdays, NY timezone
    context.time.within_business_hours &&
    context.time.day_of_week in ["Mon","Tue","Wed","Thu","Fri"] &&
    // Required VCs presented this session
    context.presented_vcs.contains("vc_provider.verified_human") &&
    context.presented_vcs.contains("vc_provider.over_18") &&
    // Per-request spend cap
    context.quoted_price_usd <= 5 &&
    // Aggregate 30-day cap
    context.spend_last_30d_usd + context.quoted_price_usd <= 50
};

permit (
    principal == Agent::"did:web:ghost.agent",
    action == Action::"discuss_scheduling",
    resource == Calendar::"ian.primary"
) when {
    context.time.within_business_hours &&
    context.schedule_window_days <= 14
};

// --- FORBID rules (override permits) ---
forbid (
    principal,
    action,
    resource
) when {
    resource.tags.contains("confidential") ||
    resource.tags.contains("client-list")
};

forbid (
    principal,
    action,
    resource
) when {
    // Expired connection
    context.time.now > connection.expires_at
};

Evaluation trace (walk-through)

Ghost sends: "Summarize the Q2 research findings doc from Project Alpha."

1. Transport:   DIDComm envelope received, connection_id = conn_7a3f
2. PDP loads:   Connection Token → Cedar policies above
3. Build context:
     principal = Agent::"did:web:ghost.agent"
     action    = Action::"summarize"
     resource  = Document::"alpha/q2-research"
                 { tags: ["research","q2"], classification: "internal" }
     context   = {
       time.now: 2026-04-22T14:30 NYC,
       time.within_business_hours: true,
       time.day_of_week: "Wed",
       presented_vcs: ["vc_provider.verified_human","vc_provider.over_18"],
       quoted_price_usd: 0.02,
       spend_last_30d_usd: 3.18,
       connection.expires_at: 2026-10-22
     }
4. Cedar.isAuthorized(...) →
     permit #1 matches (all when-clauses true)
     forbid #1 does NOT match (no confidential tag)
     forbid #2 does NOT match (not expired)
   → ALLOW, no obligations
5. Dispatch to LLM with memory filtered to Project::"alpha"
6. Generate summary
7. PDP egress re-check → ALLOW
8. Log to audit chain, charge $0.02 via x402
9. Return response to Ghost

5. Example 3 — Advanced policy (obligations, rate limits, re-prompts)

Adds egress redaction, rate limiting, and fresh-consent re-prompts for specific actions.

Layer 2 — Cedar with obligations

Cedar core doesn't have "obligations" as a first-class construct — we express them as a parallel obligation policy set evaluated after the allow decision. Structure:

// --- PERMIT (as before) ---
permit (
    principal == Agent::"did:web:ghost.agent",
    action in [Action::"read", Action::"summarize"],
    resource in Project::"alpha"
) when { /* as in example 2 */ };

// --- OBLIGATIONS (post-allow, applied as transforms) ---
// Rendered by the PDP into a side-channel result, not a Cedar primitive

@obligation("redact_fields")
@obligation_params({ "fields": ["client.name", "client.email", "client.phone"] })
permit (
    principal == Agent::"did:web:ghost.agent",
    action == Action::"read",
    resource in Project::"alpha"
);

@obligation("rate_limit")
@obligation_params({ "max_requests_per_hour": 60 })
permit (
    principal == Agent::"did:web:ghost.agent",
    action,
    resource in Project::"alpha"
);

@obligation("require_fresh_consent")
@obligation_params({ "max_age_seconds": 300, "prompt": "Ghost is requesting export of multiple files" })
permit (
    principal == Agent::"did:web:ghost.agent",
    action == Action::"bulk_export",
    resource in Project::"alpha"
);

> Note on syntax: Cedar's real syntax doesn't include @obligation annotations natively; the ARP profile extends Cedar with a parallel obligation-rules file. The PDP evaluates the permit set, then evaluates the obligation set against the same request, and returns { decision, obligations[] }.

What the PDP returns

{
  "decision": "allow",
  "obligations": [
    { "type": "redact_fields", "params": { "fields": ["client.name", "client.email", "client.phone"] } },
    { "type": "rate_limit",   "params": { "max_requests_per_hour": 60, "current": 12 } }
  ],
  "policies_fired": ["p_alpha_read", "o_redact_clients", "o_rate_limit_alpha"]
}

The agent runtime:

  1. Calls the LLM with resources scoped to Project::alpha
  2. Passes the raw response through the redact_fields transform
  3. Increments the rate-limit counter
  4. Logs everything to the audit chain

6. Full variable catalog

Variables the PDP exposes at evaluation time. Organized by category. Use these in when { ... } clauses.

6.1 Principal (the requesting agent)

VariableTypeMeaning
principal.didstringAgent DID (e.g., did:web:ghost.agent)
principal.owner_didstringThe human principal's DID
principal.agent_namestringHuman-readable name from agent card
principal.connection_idstringWhich connection this invocation runs under
principal.connection_purposestringPurpose label of the connection
principal.delegation_depthintHow many hops in the UCAN chain
principal.delegated_by[string]Full chain of delegator DIDs
principal.reputation_scoreint 0–100Optional external reputation (v0.2+)
principal.key_age_secondsintHow long the signing key has existed

6.2 Resource (what's being accessed)

VariableTypeMeaning
resource.typeenumfile, folder, project, calendar_event, contact, memory, tool, payment
resource.idstringUnique ID within its type
resource.pathstringHierarchical path
resource.projectstringParent project ID
resource.owner_didstringResource owner's DID
resource.classificationenumpublic, internal, confidential, restricted
resource.tags[string]Arbitrary labels
resource.data_categories[enum]pii, phi, financial, legal, credentials, location
resource.contains_piiboolShortcut for category check
resource.created_attimestampWhen created
resource.updated_attimestampLast modified
resource.size_bytesintFor files
resource.mime_typestringFor files

6.3 Action (what's being done)

Actions are enumerated from the scope catalog. Core set:

ActionCategory
read, list, search, summarize, deriveRead-family
write, update, create, appendWrite-family
delete, archive, purgeDestructive
share_internal, share_external, forward, bulk_exportShare-family
execute_tool, call_function, run_codeCompute
pay, refund, authorize_paymentEconomic
pair, re_consent, rotate_keys, revokeMeta
discuss_scheduling, propose_meeting, check_availabilityDomain-specific (calendar example)

6.4 Temporal context

VariableTypeMeaning
context.time.nowtimestampRequest time (ISO 8601)
context.time.hourint 0–23Hour in connection's configured TZ
context.time.day_of_weekenumMon, Tue, ... Sun
context.time.datedateISO date
context.time.within_business_hoursboolWithin connection-configured window
context.time.timezonestringIANA TZ string
context.time.is_holidayboolOptional holiday-calendar check

6.5 Connection state

VariableTypeMeaning
context.connection.created_attimestampWhen this connection was first signed
context.connection.expires_attimestampWhen it dies
context.connection.last_consent_attimestampLast human-approved refresh
context.connection.statusenumactive, suspended, expiring, revoked
context.connection.scope_resources[string]Resources the connection covers
context.connection.audit_log_sizeintHow many events logged
context.connection.total_messagesintMessages exchanged ever

6.6 Economic context

VariableTypeMeaning
context.quoted_price_usdfloatPrice of this specific request
context.spend_this_request_usdfloatSame as quoted, post-execution
context.spend_last_24h_usdfloatRolling
context.spend_last_7d_usdfloatRolling
context.spend_last_30d_usdfloatRolling
context.spend_all_time_usdfloatLifetime of this connection
context.currencyenumUSDC, ETH, etc.

6.7 Rate / frequency

VariableTypeMeaning
context.requests_last_minuteint
context.requests_last_hourint
context.requests_last_dayint
context.seconds_since_last_requestint
context.concurrent_requestsintIn-flight right now

6.8 Stated intent / purpose

VariableTypeMeaning
context.stated_purposestringFree-text purpose claimed by requesting agent
context.stated_purpose_categoryenumClassifier output: scheduling, research, payment, introduction, other
context.thread_idstringConversation thread
context.turn_numberintNth turn in this thread

6.9 Presented credentials

> Note (v0.1+): the vc_provider.* prefixes below are illustrative. The PDP treats all VC type identifiers as opaque strings. Any provider-defined VC type works identically — custom.over_18, gov.verified_human, etc. Credential providers are pluggable; the reference implementation ships no provider-specific bindings.

The keys in the table below are example VC type identifiers — they render as plain string comparisons inside Cedar when { ... } clauses. Replace them with whatever type strings your chosen VC issuer uses; the semantics are identical.

VariableTypeMeaning
context.presented_vcs[string]Full list of VC type IDs presented this session
context.presented_vcs.contains(X)boolConvenience check
context.presented_vcs.vc_provider.verified_humanbool
context.presented_vcs.vc_provider.over_18bool
context.presented_vcs.vc_provider.over_21bool
context.presented_vcs.vc_provider.countrystringISO country code
context.presented_vcs.vc_provider.us_residentbool
context.presented_vcs.vc_provider.attestation_age_secondsintFreshness
context.presented_vcs.employment.verifiedboolExternal issuer
context.presented_vcs.employment.employer_didstring

6.10 Request origin / environment

VariableTypeMeaning
context.origin.ipstringSource IP
context.origin.geo.countrystringISO country
context.origin.network_typeenumresidential, corporate, vpn, tor, cloud
context.origin.is_known_deviceboolPreviously-seen fingerprint

6.11 Risk signals

VariableTypeMeaning
context.risk.scoreint 0–100Composite risk
context.risk.signals[string]E.g., ["velocity_spike","new_device","high_value"]
context.anomaly.detectedboolBehavioral anomaly

6.12 Principal state (the human)

VariableTypeMeaning
context.principal.onlineboolReachable for consent
context.principal.do_not_disturbboolDND mode
context.principal.last_active_seconds_agoint
context.principal.recent_consent_countintAnti-fatigue signal

7. Obligation types (what policies can demand post-allow)

ObligationEffect
redact_fieldsStrip named fields from the response
redact_regexStrip anything matching regex
summarize_onlyReplace raw content with model-generated summary ≤ N words
aggregate_onlyReturn count/sum/avg instead of rows
rate_limitEnforce max-N-per-window
require_fresh_consentPrompt the human for approval; block until granted
require_vcDemand a specific VC be presented in this session
log_audit_levelForce heightened audit detail
delete_afterAttach a TTL to the response (sticky policy)
no_downstream_shareMark response as non-re-sharable
notify_principalFire a push to the human after the request
charge_usdTrigger an x402 settlement
insert_watermarkEmbed a traceable marker

8. Cedar schema (type declarations)

Cedar requires a schema declaring entity types + action groups. Published at /.well-known/policy-schema.json on each agent. Shape:

{
  "ARP": {
    "entityTypes": {
      "Agent": {
        "shape": {
          "type": "Record",
          "attributes": {
            "owner_did": { "type": "String" },
            "agent_name": { "type": "String" },
            "connection_id": { "type": "String" }
          }
        }
      },
      "Project": {
        "shape": {
          "type": "Record",
          "attributes": {
            "classification": { "type": "String" },
            "tags": { "type": "Set", "element": { "type": "String" } }
          }
        }
      },
      "Document": {
        "memberOfTypes": ["Project"],
        "shape": {
          "type": "Record",
          "attributes": {
            "classification": { "type": "String" },
            "tags": { "type": "Set", "element": { "type": "String" } },
            "data_categories": { "type": "Set", "element": { "type": "String" } }
          }
        }
      }
    },
    "actions": {
      "read":     { "appliesTo": { "principalTypes": ["Agent"], "resourceTypes": ["Document","Project"] } },
      "list":     { "appliesTo": { "principalTypes": ["Agent"], "resourceTypes": ["Project"] } },
      "summarize":{ "appliesTo": { "principalTypes": ["Agent"], "resourceTypes": ["Document","Project"] } },
      "share_external": { "appliesTo": { "principalTypes": ["Agent"], "resourceTypes": ["Document"] } }
    }
  }
}

9. Common patterns

Pattern: time-bounded access

permit (principal == Agent::"did:web:ghost.agent", action, resource)
when { context.time.now < datetime("2026-10-22T00:00:00Z") };
forbid (principal, action == Action::"bulk_export", resource)
when { context.connection.last_consent_at < context.time.now - duration("5m") };

Pattern: Reputation gate

permit (principal, action in [Action::"read"], resource in Project::"alpha")
when { principal.reputation_score >= 70 };

Pattern: Attribute-gated sharing (pluggable VC issuer)

permit (principal, action == Action::"share_external", resource)
when {
    context.presented_vcs.contains("vc_provider.us_resident") &&
    context.presented_vcs.contains("vc_provider.over_18")
};

Pattern: Spending ratchet (per-counterparty cap)

forbid (principal, action == Action::"pay", resource)
when { context.spend_last_30d_usd + context.quoted_price_usd > 50 };

Pattern: Purpose binding

permit (principal, action == Action::"read", resource in Project::"alpha")
when { context.stated_purpose_category in ["scheduling", "summarization"] };

Pattern: Blast-radius clamp (forbid bulk)

forbid (principal, action, resource)
when { resource.size_bytes > 10_000_000 };

Pattern: No-onward-sharing (sticky)

permit (principal, action == Action::"read", resource)
when { true }
advice { "no_downstream_share": true };

10. Evaluation semantics

  1. Start with decision = DENY (deny-by-default).
  2. Evaluate all permit policies. If any matches, decision = ALLOW.
  3. Evaluate all forbid policies. If any matches, decision = DENY (forbid wins over permit).
  4. Evaluate all obligation policies. Collect their params.
  5. Return { decision, obligations[], policies_fired[] }.

Evaluate both inbound (request) and outbound (response) — obligations typically fire on outbound.


11. What the owner UI actually renders

From the Example 2 Cedar, the owner's consent screen shows:

Ghost wants to connect with Samantha for Project Alpha.

Ghost WILL be able to:
  • Read, list, and summarize files in Project Alpha
  • Discuss scheduling (up to 14 days ahead)
  • Spend up to $5 per request, $50 per month

Ghost WILL NOT be able to:
  • See anything tagged "confidential" or "client-list"
  • Access your calendar details beyond availability
  • Share Project Alpha files externally

Access is limited to:
  • Weekdays, 09:00–17:00 America/New_York
  • Ghost must prove: Verified human, 18+

Connection expires: October 22, 2026

[ Approve ]    [ Adjust ]    [ Cancel ]

That rendering is generated from the Cedar policies + schema + scope catalog — not hand-written. The consent UI is a deterministic projection of the signed policy.


Source: docs/ARP-policy-examples.md · Ported 2026-04-23