On this page
Planned Loam's use of Dapr is part of the function runtime proposal (platform part 5). None of it is on the engine's main branch yet.
Dapr, the Distributed Application Runtime, gives applications a set of portable APIs (state, pub/sub, service calls, secrets and more) and lets operators decide what backs each one. Loam's function runtime plans to use Dapr's API as the interface between user functions and the platform, and Dapr's secrets building block as the one path to tenants' secret stores.
| Repositories | dapr/dapr, dapr/components-contrib |
| Docs | docs.dapr.io |
| License | Apache-2.0 |
| Version checked | v1.18.4 |
| In Loam | Planned |
How Dapr works
Dapr runs next to an application as a sidecar process, daprd, usually one per application instance. The application calls daprd over HTTP or gRPC on localhost, using Dapr's API, and daprd does the work against whatever infrastructure it is configured with.
The API is grouped into building blocks. The current docs list twelve: workflows, service invocation, pub/sub, state, bindings, actors, secrets, configuration, distributed lock, cryptography, jobs and conversation (LLM calls).
What backs a building block is a component, a YAML resource like this:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: vault
spec:
type: secretstores.hashicorp.vault
version: v1
metadata:
- name: vaultAddr
value: https://vault.example:8200The implementations live in components-contrib: dozens of state stores, message brokers, bindings and secret stores behind a common interface per building block. An application written against Dapr's pub/sub API can move from Kafka to NATS by changing a component, not code. Components can be scoped to specific application ids, and secret access can be restricted per application with allow and deny lists.
Why Loam plans to use it
A function runtime needs an API for "talk to the platform": call another function, publish an event, read state, fetch a secret. We could invent one. Dapr's API already exists, has SDKs in every major language, is familiar to many teams and is Apache-2.0. Using its shape means Dapr SDK code that sticks to the supported subset can run on Loam, and gives us a well-trodden answer for the long tail of integrations.
What we do not want is a sidecar per function. Loam's functions start in milliseconds (workerd isolates, wasmtime components) and thousands can be resident on one node. A Go process beside each one would dwarf them. So the proposal splits Dapr in two.
The proposal: a Dapr API server in Rust, plus one shared daprd
Each call carries a sandbox token or an mTLS identity.
One per cluster, off the hot path.
A new Rust server implements the subset of Dapr's gRPC service (dapr.proto.runtime.v1.Dapr) that maps onto Loam:
| Building block | Backed by |
|---|---|
| Service invocation | The gateway, to another function |
| Pub/sub | Loam streams |
| State | Loam Live or a TiKV keyspace (get and save first; queries later) |
| Bindings | Native for HTTP, cron and Loam streams; everything else forwarded to the shared daprd |
| Secrets | Dapr's secrets building block, through the shared daprd (below) |
| Configuration | The tenant's environment in Loam's control store |
| Workflow | Denied. Resonate owns durable execution in Loam |
| Actors, distributed lock | Out of scope |
Every call carries either an mTLS client certificate with a SPIFFE ID or a per-invocation sandbox token, and the server derives the tenant from that credential, never from a field in the request. It then checks the call with OpenFGA.
The Go daprd stays for what it does best: the long tail of bindings and brokers. It runs once per cluster, and the Rust server forwards those calls to it after the tenant check.
Secrets from every cloud
The owner's decision is to use Dapr's secrets building block as the single path to tenants' secrets. That gives Loam every store in components-contrib without writing a client for each. The maturity levels are Dapr's own:
| Component | Store | Dapr status |
|---|---|---|
secretstores.azure.keyvault | Azure Key Vault | Stable |
secretstores.hashicorp.vault | HashiCorp Vault (and OpenBao) | Stable |
secretstores.kubernetes | Kubernetes Secrets | Stable |
secretstores.aws.secretmanager | AWS Secrets Manager | Beta |
secretstores.aws.parameterstore | AWS SSM Parameter Store | Alpha |
secretstores.gcp.secretmanager | GCP Secret Manager | Alpha |
Every component implements the same small interface (GetSecret, BulkGetSecret), exposed by the runtime as gRPC calls and as GET /v1.0/secrets/{store}/{key}.
How it would work in Loam:
- One component per tenant and store, named after the tenant, carrying the tenant's own credentials, generated by the Loam operator from the tenant's settings. Loam's own cloud credentials never go into a tenant's component, and no two tenants share one.
- Loam enforces the scoping. With one shared
daprdthere is one Dapr app id, so Dapr's own per-app scoping cannot tell tenants apart. The Rust server takes the tenant from the credential, rewrites the function's logical store name (aws,vault) to that tenant's component, checks the key against the tenant's allow list, and only then callsdaprd. A request for another tenant's component cannot even be expressed. Dapr's scopes are still set, as a coarse second layer. Tenants who need Dapr-enforced separation too can get a dedicateddaprd. - Off the hot path. Secrets are read at instance start and on cache miss, and cached per tenant, store and key for a short TTL, in memory only, never on disk or in logs.
- Delivery per tier. workerd functions get a
SECRETSbinding whoseget(name)is a loopback call; wasmtime components import a host interface; gVisor sandboxes call the standard Dapr HTTP endpoint on their own loopback, so the ordinary Dapr SDKs work.
Limits and open questions
- Hot reload. Adding a tenant's store must not restart the shared runtime. That depends on Dapr's component hot-reload feature and its maturity in v1.18, which we still have to verify.
- Workload identity is per pod, not per tenant. An AWS IAM role for service accounts or an Azure workload identity belongs to the
daprdpod, so tenant components use their own credentials, Vault auth or cross-account role assumption where supported. - A shared
daprdholds many tenants' secret-store credentials. Loam-side scoping, per-tenant components, a dedicateddaprdon request and keepingdaprdoff the public network are the mitigations. - The Rust server does not exist yet. An early native stream service and a Rust Dapr app that forwards events from Kafka, agent topics and webhooks into Loam streams exist as uncommitted work; the owner has asked for Resonate, TiKV and Dapr to be validated together in forks before any of it lands in the engine.
The next post covers the three runtimes the function tiers would run on: workerd, wasmtime and gVisor.