Capabilities
A capability is a typed invocation surface. Every meaningful action in Xema — read a knowledge-base page, create a pull request, start a workflow run, install a biome, send a Slack message — is named by a capability reference, mediated by one gateway, and authorized against one grant table. Agents, workflows, UI buttons, the Shell, and external apps all use the same surface.
Capability refs
A capability reference is a stable, versioned name:
<domain>:<resource>.<verb>@<major>
Illustrative refs:
1kb:page.read@1
2kb:page.write@1
3artifact:read@1
4workflow:run.start@1
5workflow:control@1
6workspace:session.create@1
7workspace:session.read@1
8document:render.pdf@1
9memory:recall@1
10memory:store@1
11biome:install@1
12biome:promote@1
13biome:submit-to-store@1
14store:biome.list@1
15connector:scm.create-pull-request@1
16connector:scm.merge@1
17connector:tracker.issue.create@1
18connector:docs.publish-page@1
19connector:channel.send-message@1
20connector:llm.invoke@1
21mcp-tool:invoke@1The @<major> is intentional. Capability refs version like syscalls, not like packages: minor and patch changes to an implementation never break the ref. A major bump is a deliberate, additive event — v1 stays alive until callers migrate. This is the only thing in Xema that auto-versions; everything else respects the user-controlled versioning policy.
The gateway flow
Every invocation goes through one live mediation funnel:
1caller
2 → capability router
3 → authorization (subject + environment + capability + resource → decision)
4 → resolver (environment + binding state → which contribution serves this ref?)
5 → implementation (the bound contribution or kernel handler)
6 → audit-log (every invocation, structurally)Every call carries { ref, subject, environment, input }. Every decision lands in the audit log. Agents never hold raw credentials — the gateway resolves the binding for the active environment and calls the provider itself. The agent sees the capability ref and the input; nothing else.
The two-stage permission model
Xema pairs install-time consent (App Store / Google Play) with runtime brokered access (Flatpak / XDG portals). Both stages are mandatory in production environments.
Stage 1 — install time
The biome manifest declares:
requiresCapabilities[]— every ref the biome may ask for.permissionHints[]— a human-readable reason per capability.defaultProfile— the built-in permission profile that fits.
The Xema Store computes a PermissionDigest: capabilities grouped by domain, a risk tier, a data-access summary, and a diff against the previously installed version. An org admin approves the digest, optionally applying a built-in profile (e.g. read-only-assistant, support-chatbot, internal-agent, connector-bridge, unrestricted) or customizing per-capability resource globs, environments, and rate limits. The result is a recorded BiomeInstallGrant.
Stage 2 — runtime
Every capability call is authorized against the grant before it reaches the implementation. The gateway checks:
- Is the capability in the biome's
BiomeInstallGrant? - Is the named resource reachable through the active resource authority and ownership rules?
- Is the environment in the grant's allowed environment set?
- Is the subject covered (direct identity, group, or role)?
- Is the audience policy compatible?
- Within rate and quota?
- Does the grant require human approval for this call?
Allowed → invocation proceeds. Denied → fail-fast with a structured response. Either way, audited.
Structured denials
Denial is a typed response, not a stack trace. Every denial carries an auditId the caller can pass to the Shell's why-denied <auditId> to get back:
1{
2 "auditId": "deny_123",
3 "subject": { "kind": "agent", "ref": "agent:support-bot" },
4 "capability": "workflow:run.start@1",
5 "environment": "public-session",
6 "decision": "denied",
7 "reasons": [
8 {
9 "code": "MISSING_GRANT",
10 "detail": "workflow:run.start@1 is not granted in public-session"
11 }
12 ],
13 "suggestions": [
14 { "kind": "request-grant", "capability": "workflow:run.start@1", "environment": "app" },
15 { "kind": "switch-environment", "from": "public-session", "to": "project" }
16 ]
17}Agents can self-correct (switch environment, ask a human to grant the capability) without trial-and-error. Humans get the same data, formatted.
Worked example — connector capability
A workflow that opens a pull request looks like this:
- The workflow step declares
connector:scm.create-pull-request@1as a required capability. - The biome manifest exposes (or requires) the same ref.
- At runtime the workflow runner asks the gateway: "open a PR from branch
feature/xintomain, title…, description…." The request names no repository: it is the one bound to the project the workflow runs in. - The gateway checks the
BiomeInstallGrantfor the workflow's subject, finds the connector binding allowed for theprojectenvironment, and calls the GitHub connector with the org's stored credentials. - The agent never sees the GitHub token. The response is the change-request record: its id, the two branches, the title and its status.
The same workflow YAML works against GitLab or Gitea if the org's connector binding for that resource points there — the workflow names no provider.
Service-to-service auth — Keycloak-issued tokens
Every cross-service call in the Xema layer authenticates with a Keycloak service-account access token, not a static shared secret.
The model is uniform:
- At boot, each service registers itself with the Xema Identity Service via
IdentityBootstrapService(@xemahq/identity-client). The identity service provisions a Keycloak OAuth2 client (one per service) and returns theclient_id+client_secret+token_endpoint. - On every outbound call, the service requests an access token via the
Keycloak
client_credentialsgrant. Tokens are cached in-process and refreshed at ~80% of declared lifetime (with jitter to desync replicas). - The caller sends
Authorization: Bearer <token>. The receiver's globalJwtVerificationGuard(from@xemahq/platform-common) validates the signature against the realm JWKS, checks issuer and expiry, and rejects on any failure. There is no second, shared static-secret check. - The only legacy
IDENTITY_API_INTERNAL_TOKENenv var still in service configs is the one-time bootstrap secret the identity service uses to authenticate the initial registration request. Once the service holds Keycloak credentials it doesn't carry any other static internal token.
Fail-fast: if a service cannot mint a Keycloak token at the moment of a non-best-effort outbound call (e.g. capability invocation), the call errors out. There is no static-token fallback. Best-effort observability paths (audit-log appends) log and continue.
This matches the per-allocation worker-token model used by the agent runtime: same Keycloak realm, same JWKS, same guard — just a different client per caller.
The six meta-tools — how agents see capabilities
Agents never see individual MCP servers or per-biome tool surfaces. They see exactly six meta-tools — search, describe, invoke, plan, preflight, explain — and discover everything else dynamically:
| Meta-tool | What it does |
|---|---|
xema_capabilities_search | Retrieves the capabilities the calling agent is both armed and authorized to invoke in the current Execution Context. All arguments are flat and optional: { query?, domain?, resourceType?, mutating?, limit?, cursor? }. |
xema_capabilities_describe | Returns the full schema for one or more refs: { ref, inputSchema, outputSchema, examples, sideEffects, requiresApproval, biome: { id, version } }. Accepts an array of up to 50 refs in one call. |
xema_capabilities_invoke | Generic invocation: { ref, input } → { output, auditId, obligations }. input is validated against the capability's full declared JSON Schema at the gateway boundary before runner dispatch. |
xema_capabilities_plan | Derives the shortest runnable sequence to a goal capability over the capability graph: { goalCapabilityRef, fromResourceTypes, maxDepth? } → { goal, found, steps, missingResourceTypes }. |
xema_capabilities_preflight | Checks readiness before a call can fail: { ref } → { ready, requirements, blockers } — missing credentials, grants, or runtimes. |
xema_capabilities_explain | Turns a denial code from a failed invoke into the exact grant that unlocks it: { capabilityRef, denialCode } → { permissions, domain, biome, suggestions }. |
There is no full-catalogue listing. search returns only what the caller may actually invoke — denied capabilities are absent, not flagged, and there is no "include denied" switch. Retrieval is graph-scoped: give it an anchor (resourceType, or a noun in query that names a known resource type) and it walks that type's capability-graph neighbourhood, which is the fast, high-precision path. Without an anchor it falls back to a bounded, paginated catalogue query — page through it with cursor, taken from the previous response's nextCursor.
Worked example — an agent discovers and calls a capability:
1// 1. Discover (flat args — no filter wrapper)
2xema_capabilities_search({ domain: "connector" })
3// → {
4// "capabilities": [
5// { "ref": "connector:scm.create-pull-request@1", "biome": "software-dev", ... },
6// { "ref": "connector:channel.send-message@1", "biome": "slack", ... } // also provided by the Telegram connector
7// ],
8// "anchor": { "resourceType": null, "source": "none" },
9// "consideredCount": 2
10// }
11
12// 2. Describe (batched)
13xema_capabilities_describe({ refs: ["connector:scm.create-pull-request@1"] })
14// → [{ "ref": "...", "inputSchema": { ... }, "examples": [ ... ] }]
15
16// 3. Invoke — input must match the declared schema exactly.
17// `sourceBranch`, `targetBranch` and `title` are required; `description` is optional.
18// The repository is not an input: it is the one bound to the calling session's project.
19// This capability requires approval, so it runs only once that approval is given.
20xema_capabilities_invoke({
21 ref: "connector:scm.create-pull-request@1",
22 input: { sourceBranch: "feature/x", targetBranch: "main", title: "Add user authentication" }
23})
24// → {
25// "output": {
26// "id": "cr-1", "externalId": "42",
27// "sourceBranch": "feature/x", "targetBranch": "main",
28// "title": "Add user authentication", "status": "open",
29// "createdAt": "2026-01-15T10:00:00Z", "updatedAt": "2026-01-15T10:00:00Z"
30// },
31// "auditId": "inv_abc"
32// }The connector:channel.send-message@1 capability in the discovery example is a family contract: the conversation is served by whichever provider its bound connection uses, Slack or Telegram. The caller names the conversation and the reply, never the provider.
Adding a new biome or MCP server expands the search result without changing the agent's tool surface. Agents adopt new capabilities at runtime; no prompt rebuild required.
Invocation input is strictly validated
invoke validates input against the capability's full declared JSON Schema — types, enums, and string formats are enforced, and unknown properties are rejected. Nothing is coerced: a string is not silently parsed into a number, and an extra or misspelt field is not dropped. A violation is a fail-fast 400 that names the offending JSON path, so an agent can correct the exact field rather than guess. Callers must send exactly the shape describe returns.
External MCP servers as capability providers
Xema does not show an agent the union of every MCP server's tools/list. Instead, external MCP servers are registered through the MCP gateway as capability providers:
- Admin registers an external server (e.g. an organisation's Slack MCP, a Jira MCP, a Notion MCP).
- At registration time, the MCP gateway calls the external server's MCP
tools/list. - Each external tool is translated to a capability ref:
<provider-id>:<tool-name>@1. - The capability is inserted into the Service Registry with runner kind
mcp-external. xema_capabilities_searchsurfaces the provider's capabilities under the same policy + grant model as any first-party capability — and only when the Agent is armed and its subject is authorized to invoke them.- On
xema_capabilities_invoke, the gateway translates the call back to an MCPtools/callagainst the registered server.
The agent's view stays uniform: every capability — first-party, biome-shipped, or externally federated — is a ref behind the same six meta-tools. Policy, audit, and grant flows are identical.
Capability lifecycle
Each capability ref carries a lifecycle state that constrains how it may be invoked:
| State | Meaning |
|---|---|
proposed | Declared in a manifest but never seeded; search omits it |
seeded | Registered in the Service Registry; eligible for grants |
published | Granted, invocable, and returned by search for authorised subjects |
deprecated | Still invocable; search marks it; describe carries a replacedBy ref |
retired | No longer invocable; the gateway returns a structured denial pointing at the successor |
Lifecycle is per-major-version. Bumping @1 → @2 introduces a new ref; the @1 ref enters deprecated and the manifest declares replacedBy: "...@2".
See also
- MCP and Capabilities — deeper detail on the six-meta-tool flow and external MCP server registration.
- Execution Contexts — the per-invocation envelope every capability call carries.
- Policy — the decision protocol every invocation is gated by.
Previous: ← Objects Next: SDK / Getting Started →