Skip to content

Permissions & access model

How Martha decides who you are (authentication) and what you may use (authorization). The CLI guide lists the grant commands; this page is the model they operate on. If you've ever hit "why can't my agent call this tool?", the answer is almost always here.

One sentence: Keycloak establishes the principal (a tenant + a Client or an Agent); Martha's grant tables are ACLs that say which capabilities that principal may invoke. There is no per-user tool permission.

Identities

IdentityWhat it isExample
TenantThe isolation boundary. Every definition, grant, session, and message is scoped to one tenant. A string id.acme
ClientAn API-key / chat consumer within a tenant. An integer id. Tools are granted to clients.my-bot
AgentAn AgentDefinition (system prompt + LLM config) with its own identity. A UUID. Tools can also be granted to agents.support-bot
Human userA Keycloak user. Has roles, not tool grants.you

A tenant has many clients and agents. Client.id (int) is not the tenant — it's a resource inside one.

Authentication (Keycloak, realm frank)

Three principals authenticate three ways:

  • Human → client frank-low-code, browser PKCE or password grant. martha auth login (or --username/--password). Tokens are short-lived (~5 min).
  • Service account (CI) → client_credentials. MARTHA_CLIENT_ID/MARTHA_CLIENT_SECRET + martha auth login --service-account, or export a pre-issued MARTHA_TOKEN. Auto-refreshes.
  • Agent → a per-agent confidential Keycloak client martha-agent-{uuid} (created when the agent is provisioned for auth) via client_credentials, or an martha_ak_… API key. Either resolves to sub="agent:{id}", is_service_account=true.

A token carries a required tenant_id claim (missing → 403) and realm roles. Roles gate privilege:

  • admin — tenant admin.
  • martha-super-admin — platform admin (cross-tenant).
  • client:{client_key} — explicit access to one client.

Humans vs service accounts matters: privileged, human-only actions (e.g. resolving an approval) require a human token; agents and service accounts are rejected at the auth layer.

Authorization: catalog ≠ grant

Two different things, often confused:

  • Catalogfunction_definitions / workflow_definitions. A capability exists in your tenant (platform functions are auto-seeded). This is not permission.
  • Grant — a row in a junction table. A specific Client or Agent may invoke a capability.

An agent does not see a tool just because it's in the catalog. It needs an explicit grant. "Registered" ≠ "usable."

Grants attach to a Client or an Agent — never to an individual human user. There is no UserFunctionAccess. A human's roles only decide which client they may drive; the tools then come from that client's (and, in chat, the agent's) grants.

Grant the access

bash
# grant a capability to a client (type ∈ function | workflow | agent | mcp)
martha clients grant my-bot function search_documents
martha clients grant my-bot agent  support-bot          # which agents this client may invoke

# collection-scoped grant (the function only sees that collection + descendants)
martha clients grant my-bot function list_docs --collection reports

# grant to an AGENT (chat / agent-loop surface)
martha agents add-function search_documents              # + --collection <slug-or-id>

martha clients revoke my-bot function search_documents
martha clients access my-bot                              # show all active grants

Grants are an allowlist (no per-row deny) and fail closed: no matching grant → the call is denied (a clean 403-style result), never a crash. Collection-scoped grants resolve by most-specific ancestor across the union of client and agent grants.

The two-surface rule (read this before debugging a 403)

The same function is authorized differently depending on how it's invoked:

InvocationRuns asGrant it needsCLI
Chat / agent-loopthe agent (+ optional linked client)AgentFunctionAccessmartha agents add-function
Workflow function-nodethe triggering caller's Client (run-as)ClientFunctionAccessmartha clients grant … function …
REST / direct callthe Client (API key / token)ClientFunctionAccessmartha clients grant …

The classic trap: you agents add-function X, the agent calls X fine in chat, but a workflow that uses X returns a 403. Workflow nodes run as the client, not the agent — grant the client (clients grant). An agent grant is inert for workflow nodes.

This separation is deliberate: revoking an agent's tool grant never changes how workflows execute, and vice versa.

Tenant isolation

Everything is filtered by tenant_id. Grants are implicitly tenant-scoped (a grant references a definition that belongs to a tenant). A principal in tenant A can never resolve, see, or invoke anything in tenant B. Platform functions (source='platform') are the one shared catalog — seeded once and visible to every tenant — but still require a grant to invoke.

See also

  • Surfaces & capability keys — how a chat client/embed declares what it can render and answer.
  • Capability approvals — gating risky capabilities behind human approval.

Martha is built by aiaiai-pt.