Extensions and Capability-Gating

Alongside the message, every operation carries typed extensions: the contextual state policy reasons about.
On this page

Alongside the message, every operation carries typed extensions: the contextual state policy reasons about. Identity, security labels, the delegation chain, request headers, and agent session context are extensions. Each extension is bridged into the flat attribute bag APL reads, under a well-known namespace. Capability-gating controls which plugins may read or write each one.

Hosts rarely configure extensions directly. Capability gating restricts the state available to plugins that execute APL effects. The namespaces below are the exact keys an APL predicate or plugin may read. The per-type absent-value contract, original vs flattened keys, and the catalog of every key the bridge emits are in CMF extensions and the attribute bag.

The extensions

Each extension flattens into bag attributes under its namespace, gated by a read capability. A prefix ending in . matches any key beneath it (role. matches role.hr); a bare name is an exact key.

ExtensionCarriesBag namespaceRead capability
Security (subject)subject id and type, roles, permissions, teams, claims, authentication statussubject.id, subject.type, authenticated, subject.roles, subject.permissions, subject.teams, role.*, perm.*, team.*, claim.*read_subject, read_roles, read_permissions, read_teams, read_claims
Security (client)OAuth application identity: client id, trust level, roles, permissions, scopes, audiences, teams, claimsclient.*read_client
Security (workload)attested workload identity (SPIFFE / mTLS) for the inbound caller and for this instancecaller_workload.*, this_workload.*read_workload
Security (labels)taint / classification labels for information-flow controlsecurity.labels, security.classificationread_labels, append_labels
Delegationdelegation depth, delegated flag, origin and actor subjects, chain agedelegation.*, delegatedread_delegation, append_delegation
Agentsession, conversation, turn, and lineage contextagent.*read_agent
Metaentity metadata: type, name, tags, scope, propertiesmeta.*read_meta
Requestenvironment, request id, timestamp, trace and span idsrequest.*read_request
HTTPrequest line (method, path, host, scheme) and request/response headers (lowercased)http.method, http.path, http.host, http.scheme, http.request_headers.*, http.response_headers.*read_headers, write_headers
LLMmodel id, provider, capabilitiesllm.*read_llm
MCPtool, resource, or prompt metadatamcp.* (mcp.tool.*, mcp.resource.*, mcp.prompt.*)read_mcp
Completionstop reason, token counts, model, latencycompletion.*read_completion
Provenancesource, message id, parent idprovenance.*read_provenance
Frameworkagentic framework name and version, node and graph ids, metadataframework.*read_framework
Customfree-form host-defined namespacecustom.*read_custom
Raw credentialsinbound tokens and minted delegated tokensflow through plugin payloads, not the bagread_inbound_credentials, read_delegated_tokens
Candidate constraintfolded backend routing constraint from restrict effectsnot a bag namespace — read by the host routerwritten by the policy engine

The request arguments and response body are also flattened. An object writes args.<dotted> / result.<dotted>; a top-level scalar or scalar array writes the bare key args / result. The route name is route.key. APL field pipelines (args: / result:) operate on those. Operator-maintained static attributes are flattened under data.* — these come from config files, not the request, and need no capability (see Static Attributes).

Most extensions are inputs — resolved before policy runs and flattened into the bag for predicates to read. The candidate constraint is the exception: it is an output. APL restrict effects fold into it (see Backend Restriction), it rides the returned extensions the same way minted delegation tokens do, and the host router reads it typed to prune its candidate set. Because PPE links the router in-process, this is a typed value, not a serialized blob. Capability-gating the write is not yet applied — the policy engine is its only writer today.

Capabilities

A plugin declares the capabilities it needs. PPE filters the extensions before handing them to the plugin, so a plugin sees only what it declared. The default is no access; capabilities are additive grants.

plugins:
  - name: audit-log
    kind: audit/logger
    hooks: [cmf.tool_pre_invoke]
    capabilities:
      - read_subject
      - read_client
      - read_delegation

Read capabilities and the bag keys they unlock

CapabilityUnlocks
read_subjectsubject.id, subject.type, authenticated
read_rolessubject.roles and role.* aliases for undotted members (plus the read_subject baseline)
read_permissionssubject.permissions and perm.* aliases for undotted members (plus baseline)
read_teamssubject.teams and team.* aliases for undotted members (plus baseline)
read_claimsclaim.* (plus baseline)
read_clientclient.*
read_workloadcaller_workload.*, this_workload.*
read_delegationdelegation.*, delegated
read_agentagent.*
read_metameta.*
read_requestrequest.*
read_headershttp.method, http.path, http.host, http.scheme, http.request_headers.*, http.response_headers.*
read_llmllm.*
read_mcpmcp.*
read_completioncompletion.*
read_provenanceprovenance.*
read_frameworkframework.*
read_customcustom.*
read_labelsthe labels on the security extension. A plugin reads them from the extension; security.labels in the bag is what an APL predicate reads
read_inbound_credentialsno bag keys; gates raw inbound tokens in the plugin payload
read_delegated_tokensno bag keys; gates minted tokens in the plugin payload

read_roles, read_permissions, read_teams, and read_claims each imply the read_subject baseline (subject.id, subject.type, authenticated). The last three capabilities widen no plugin’s bag view: labels are read from the typed extension, and credential material flows through plugin payloads rather than the bag. APL predicates read security.labels from the bag directly, which is how security.labels contains "secret" works (see Session Taint).

Membership names containing . remain atomic values in the canonical sets and do not receive flattened aliases. For example, test a dotted role with subject.roles contains "admin.readonly"; there is no role.admin.readonly key. The same rule applies to subject permissions and teams, and to client roles and permissions.

Write capabilities

Four capabilities grant write tokens rather than read access:

CapabilityGrants
append_labelsadd a taint or classification label (monotonic; cannot remove)
append_delegationextend the delegation chain (monotonic)
write_headersrewrite request and response headers (implies read_headers)
write_candidate_constraintnarrow the backends the router may select

Reading a candidate constraint is ungated: the host consumes it after the pipeline rather than through a filtered plugin view. If a second writer is ever introduced, composition should be monotonic, allow-sets intersecting and deny-sets unioning, so no writer can weaken another’s constraint.

Gating an action rather than a slot

perform_http is the odd one out. Every capability above gates a slot of contextual state, widening or narrowing what a plugin can see and set. perform_http gates an action: reaching outside the process at all.

It is the one capability where withholding it stops the call rather than degrading it. A plugin denied read_claims sees fewer attributes and carries on; a plugin denied its IdP call but carrying on regardless would decide without the answer it needed, which fails open. The engine therefore refuses to start and names the plugin and the capability to add.

Any plugin that fetches JWKS, exchanges a token, or dispatches a CIBA prompt must declare it. See Builtins for how the bundled ones do.

Mutability tiers

Extensions differ in how they may change during a request, and the runtime enforces the tier:

  • Immutable: fixed once resolved. The verified subject identity, client, workload, agent, meta, request, LLM, MCP, completion, provenance, and framework extensions.
  • Monotonic: may only grow. Security labels (added via append_labels, never removed) and the delegation chain (extended via append_delegation).
  • Mutable: may be rewritten. HTTP headers (via write_headers) and the custom namespace.

A plugin cannot clear a Session Taint label or rewrite a verified identity even if it holds the corresponding read capability. This keeps the state that APL depends on trustworthy: the model is untrusted, and so is any plugin beyond the context and mutations it was explicitly granted.

APL integration

Capability-gating runs at the boundary between the manager and each plugin (filter_extensions in praxis-policy-core decides which extension slots a plugin sees; the CMF extractors then flatten those slots into the bag). The same filtered, tier-enforced view feeds the attribute bag APL evaluates, so a policy and the plugins it invokes operate on a consistent, least-privilege picture of the request. See Identity for how the subject is populated and Session Taint for the monotonic label tier in action.

Next

  • Crates: map the runtime and extension APIs to workspace crates.
  • Builtins: review the bundled plugins, PDPs, and session store.