Configuring KERIpy Components, KERIA Agents, and Well-known URIs
How do you configure, publish, discover, and connect KERI components? Whether witnesses, verifiers, KERIA deployments, mailboxes, or general KERI controllers, configuration touches the topics of node publication, discovery, and connection, data discovery, and service endpoints. Node discovery and connection occurs with out of band identifiers (OOBIs). Data discovery of ACDC schemas and contents also utilize with OOBIs. Service endpoint configurations combine location scheme records and endpoint role authorizations. This article explains these concepts, KERI configuration mechanisms, and how to use them for real world, production configurations.
Disclaimer: This article assumes some basic familiarity with KERI concepts including identifiers (AIDs), witnesses, OOBIs, and the difference between KERIpy (the KERI implementation) and KERIA (the agent service). You should be able to follow along if you have some experience with KERIpy, the KLI, or KERIA and SignifyTS. If you are new to KERI and KERIA then go to the vLEI Trainings repository and work through those trainings to have a proper introduction and then return to this configuration guide.
Source review update, July 2026: The KERIA tock and escrow sections below were checked against the KERIA 0.4.1 development source with KERIpy 1.2.13 and against the current KERIpy development source. KERIA 0.3.0 with KERIpy 1.2.6 has the same central tock wiring and the same indefinitely retried multisig escrow families described here.
Introduction: The Configuration Challenge
Configuring KERIpy controllers and KERIA agents occurs at multiple levels:
Node Configuration
- Controller Configuration (KERIpy) - How your local identifier finds witnesses and peers, and which ports it listens on, if any.
- This is also used as the basis for verifier configuration in components like sally or the vLEI Verifier.
- Witness Configuration (KERIpy) - The endpoints witnesses listen on
- Agency Configuration (KERIA) - Global settings for the KERIA service
- Agent Configuration (KERIA) - Per-agent settings within KERIA
- Mailbox Configuration (KERIpy) - Where receipts and messages are delivered to
Content Hosting Configuration
- ACDC Schema Hosting (KERIpy) - Hosting ACDC schemas for resolution by the OOBI URL resolution process.
- ACDC Content Hosting (KERIpy) - Hosting ACDC content for resolution by the OOBI URL resolution process.
Well Known Discovery Configuration
- Well-known URIs for Hosted Components -
/.well-known/keri/oobi/general discovery endpoints for KERI OOBIs, ACDC schemas and witness infrastructure
Let's explore each, starting with file-based configuration and progressing to runtime endpoint role authorization.
Part 1: KERIpy Controller Configuration
Controller configuration applies to components like verifiers, such as the sally verifier service that runs its own direct-mode listeners, or the KERIA service that listens on three ports.
You specify a configuration file with the kli init command like so:
and in your config directory you would need to hav the following directory tree:
config-dir\
- keri\
- cf\
- demo-witness-oobis.json
The Configer Class
At the heart of KERIpy configuration is the Configer class in keri/app/configing.py. It's a specialized file handler supporting four serialization formats:
"""
Habitat Config File
Supports four serializations: HJSON, JSON, MGPK (MsgPack), and CBOR
The serialization is determined by the file extension (.json, .mgpk, .cbor)
"""
=
# Creates file at path like: ~/.keri/cf/main/conf.json
Configuration files follow this structure:
Example: Sally verifier - configuring listening endpoints
The "sally" property of the JSON object corresponds to the --alias argument to kli incept. The curls section of the sally object tells the verifier which port to listen on.
Located at your config_dir/keri/cf/sally.json you would find the following JSON. And the sally.json at the end of the path is really the <alias>.json where alias corresponds to the --alias argument to kli incept.
The iurls section is for witness OOBIs. The durls section is for data OOBIs, usually ACDC schemas or ACDC components. This example config does not have wurls for well knowns.
Example: KERIA agent server - configuring listening endpoints
Similar to the Sally verifier, the keria property in the config corresponds to the value passed to the --name argument to keria start which defaults to keria.
Again, you see the curls section for configuring the HTTP port used for listening to CESR transmissions to an agent. It has iurls and then KERIA-specific configuration for async loop repeat times, called "tocks."
What KERIA tocks actually configure
A tock is a scheduler interval, not an escrow timeout. It controls how soon an HIO task is eligible to run again. It does not determine how old an escrow record may become before KERIA discards it.
KERIA's root Doist uses a fixed 0.03125-second cycle. A task with a tock of 0.0 is therefore eligible on every scheduler cycle—up to roughly 32 cycles per second when the process has enough capacity. A task with a tock of 1.0 is eligible about once per second.
This matters because every missing KERIA Agent tock defaults to 0.0. If tocks.escrower is absent, misspelled, or unavailable because the configuration did not load, KERIA may run its complete central escrow pass on every root scheduler cycle.
KERIA recognizes the following exact, case-sensitive keys:
| Config key | Task | What its interval controls |
|---|---|---|
initer | Initer | One-time agent readiness and logging check. |
querier | Querier | Parent queue for key-state queries. Spawned query tasks retain their own scheduling. |
escrower | Escrower | Central KEL, TEL, reply, EXN, credential-verifier, registrar, and credentialer escrow pass. |
parser | ParserDoer | Processing of incoming CESR messages. |
witnesser | Witnesser | Witness receipt and catch-up request queue. |
delegator | Delegator | Queue that starts delegation work; not the internal delegation escrow loop. |
exchangeSender | ExchangeSender | Parent queue for sending EXN messages. |
granter | Granter | IPEX grant queue and the GrantDoer tasks it creates. |
admitter | Admitter | IPEX admit queue. |
groupRequester | GroupRequester | Queue that starts multisig Counselor work; not the Counselor escrow loop. |
seeker | SeekerDoer | Credential indexing cues. |
exchangecue | ExchangeCueDoer | EXN indexing and query cues. |
Unknown keys are ignored. Missing known keys become 0.0. KERIA currently does not validate these names or expose the effective tock mapping from GET /config, so spelling errors can silently change scheduling behavior.
Do not set every task to 1.0 merely because that is appropriate for the central escrow scan. Parser, witness, query, and message-delivery latency have different requirements. The defensible production baseline from the KERIA source is to set the central escrow interval explicitly:
"tocks":
Increasing escrower can reduce CPU spent rescanning retained records, but it also increases retry latency. It does not limit escrow size or expire stuck records.
What the central Escrower processes
Each central Escrower pass invokes all of these processors:
- KERIpy
Keverycore KEL and receipt escrows - KERIpy delegable-event escrow processing
- KERIpy
Regerylocal TEL escrows - KERIpy
Reveryreply escrows - KERIpy
Teverynon-local TEL escrows - KERIpy
Exchangerpartially signed EXN escrows - KERIpy credential
Verifierdependency escrows - KERIA registry and credential
Registrarescrows - KERIA credential-completion escrows
The single escrower tock retimes this entire group. It does not retime two important escrow processors:
- KERIpy's multisig
Counselorscans group partially signed, delegated, and partially witnessed escrows every0.5seconds. - KERIA's delegation
Anchorerscans delegated partial-witness and unanchored escrows every0.5seconds.
groupRequester and delegator only control the queues that start work. They do not control those internal escrow scans.
Escrow retry interval versus escrow lifetime
Many low-level KERIpy escrows do have age checks. In KERIpy 1.2.13, the important defaults include:
| Escrow family | Default lifetime |
|---|---|
| KEL out-of-order events | 1,200 seconds |
| KEL partial signatures, witness receipts, and delegation | 3,600 seconds |
| Unverified receipts and likely-duplicitous events | 3,600 seconds |
| Query-not-found | 300 seconds |
| Reply messages | 3,600 seconds |
| Partially signed EXN messages | 20 seconds |
| Credential dependency verification | 3,600 seconds |
| Transaction-state broker escrows | 3,600 seconds |
Other important escrow families have no age-based timeout:
| Escrow family | Tables | Retry behavior |
|---|---|---|
| Group multisig Counselor | gpse, gdee, gpwe | Full scan every 0.5 seconds until protocol completion or manual clearing. |
| KERIA delegation Anchorer | dpwe, dune | Full scan every 0.5 seconds until completion or manual clearing. |
| TEL out-of-order and anchorless events | oots, taes | Retried by Tevery without an age check. |
| KERIA registry workflow | tpwe, tmse, tede | Retried at the configured central escrower cadence. |
| KERIA credential completion | cmse | Retried at the configured central escrower cadence. |
Here, "manual clearing" describes a storage-level possibility, not a supported general KERIA administration workflow. The generic KERIpy escrow commands can reach the Baser-side group and delegation tables, but they do not correctly open the KERIA Reger that contains the TEL, registry, and credential tables.
This distinction is operationally important. A failed multisig operation can remain in one of these tables indefinitely. Every scan touches it again. As the tables grow, repeated scans consume CPU; because LMDB is memory-mapped, touching more database pages can also increase resident memory.
Agency config versus persisted per-agent config
KERIA copies the agency configuration into a separate configuration file when it creates each Signify controller's agent. The per-agent configuration is named with the Signify controller AID and is loaded again when that agent reopens.
Changing the agency's keria.json does not rewrite existing per-agent files. This means adding "escrower": 1.0 to the deployment template fixes newly created agents but may leave existing agents running their older persisted configuration. Inspect and migrate the persisted per-agent files when changing tocks for an existing deployment.
Inspecting and clearing KERIA escrows
KERIA itself does not currently provide a general escrow administration API. GET /escrows/rpy is read-only and covers reply escrows only. Deleting a long-running operation or notification does not clear protocol escrows.
The underlying KERIpy version includes kli escrow list and kli escrow clear, but these are generic Habery maintenance commands, not KERIA-aware escrow administration tools.
For the KERIA versions reviewed, the commands can open an Agent's KEL Baser when --name is the Signify controller AID and --base matches KERIA's base. They do not correctly open that Agent's TEL and credential Reger. KERIA names the Reger agent-<controller-aid> and places it under the configured base, whereas KERIpy 1.2.12 and 1.2.13 derive the Reger name from the Habery name and omit the base.
Consequently:
kli escrow listmay accurately report Baser-side KEL, multisig, delegation, reply, and EXN escrows while reporting empty or unrelated TEL, registry, and credential escrows.- Running
listmay create an empty Reger at the incorrectly derived path. kli escrow clearmust not be described as clearing all of a KERIA Agent's escrows. In KERIpy 1.2.12 and 1.2.13 it clears the real Baser but not the real KERIA Reger. Current KERIpy development clears only the Baser.
Do not use either command as proof that a KERIA Agent has no remaining escrows, and do not recommend kli escrow clear as a complete KERIA recovery procedure. If Baser-side maintenance is deliberately required, stop KERIA, snapshot the complete volume, and test against a restored copy. Accessing or clearing the actual KERIA Reger requires version-specific tooling that uses the exact KERIA Reger name and base; KERIA currently provides no supported general-purpose API or CLI for that operation.
The long-term production fix is not merely a larger tock or a periodic blanket clear. KERIA needs per-family metrics, oldest-record ages, bounded scan batches, and protocol-aware quarantine or expiry for escrow families that currently retry forever.
Configuration URL Types:
curls(Controller URLs): URLs + ports on which to set up either a TCP or HTTP port listener - where this controller can be reachediurls(Introduction URLs): OOBI URLs for witnesses, watchers, mailboxes, and any KERI controller to resolve at startupdurls(Data URLs): OOBI URLs for static data like ACDC schemas or credentialswurls(Well Known URLs): OOBI URLs specifically for well known components
Part 2: Witness Configuration
Witnesses are non-transferable identifiers that provide receipt services. Their configuration differs from controllers:
Witness Endpoint Configuration
Witnesses need two types of configuration:
- Controller endpoints - Where the witness accepts events
- Location schemes - The URLs where the witness is reachable
In this case the wan configuration property corresponds to the --alias property passed to the kli incept or kli witness start command. A witness listens on both a TCP and an HTTP port, as shown in the curls section below.
Configuring Witness Endpoints with KLI
After starting a witness with kli witness demo, configure its endpoint roles and location:
Add witness endpoint role:
Add location schemes for both TCP and HTTP:
# TCP location
# HTTP location
Witness Discovery
Controllers discover witnesses through:
- Configuration
iurls: Pre-configured witness OOBIs - OOBI resolution: Resolving witness OOBIs at runtime
- Well-known endpoints: Using
.well-known/keri/oobi/discovery
The witnesses must have their endpoint role authorizations configured so the controller can find them.
Part 3: Mailbox Configuration
Mailboxes are where witnesses and other agents deliver messages. Configuration involves:
Agent Endpoint Roles
An agent acts as a mailbox by having the mailbox endpoint role authorized:
# Configure agent with multiple roles
=
# Controller role
# Location
# Agent role (for controller to reach agent)
# Mailbox role (for witnesses to deliver messages)
# Controller processes too
Adding Endpoint Roles with KLI
The KLI provides commands to add endpoint roles without writing Python code:
Add a controller endpoint role:
Add a witness endpoint role:
Add an agent/mailbox endpoint role:
Add a location scheme for an endpoint:
Querying Endpoints with KLI
Query configured endpoints for an identifier:
Output shows all endpoint roles and their locations:
Querying Endpoints Programmatically
Controllers can query their configured endpoints:
=
# Returns:
# {
# 'agent': {
# 'EBErgFZ...': {'http': 'http://127.0.0.1:6666'}
# },
# 'controller': {
# 'EGadHcy...': {'http': 'http://127.0.0.1:7777'}
# },
# 'mailbox': {
# 'EBErgFZ...': {'http': 'http://127.0.0.1:6666'}
# },
# 'witness': {
# 'BN8t3n1...': {'http': 'http://127.0.0.1:8888'}
# }
# }
Part 4: KERIA Agency Configuration
KERIA introduces a two-level configuration model: agency-level and agent-level.
Agency Configuration: KERIAServerConfig
The agency configuration is defined in keria/app/agenting.py:
"""Agency-wide configuration"""
# HTTP ports
: = 3901
: | None = 3902
: = 3903
# Master controller
: =
: =
: = None
: =
: = None
# TLS
: = None
: = None
: = None
# Logging
: =
: = None
: = False
# Agency settings
: = True
: = 86400
# Global OOBIs applied to all agents
: =
: =
: =
Key Agency Configuration Fields:
- Ports: Three HTTP servers (admin, boot, main)
- Master controller: Agency's own identifier credentials
- Global OOBIs: Applied to every agent created
curls: Controller service endpoint OOBIsiurls: Introduction OOBIs for witnesses/watchers/mailboxesdurls: Data OOBIs for schemas/credentials
Creating an Agency
=
=
Agency Configuration File
Agencies can also load from a config file:
=
=
AS shown earlier, the config file follows the same structure as KERIpy controller configs:
Part 5: KERIA Agent Configuration
Each agent in KERIA gets its own configuration derived from the agency configuration.
Agent Configuration Loading
When creating an agent, the agency loads configuration:
"""
Loads configuration for an agent by:
1. Reading agency configuration
2. Extracting agency subsection
3. Renaming it to agent-specific name
4. Merging with environment variable OOBIs
"""
=
=
# Rename agency section to agent section
= f
=
=
del
=
# Set up curls for agent (controller-specific)
=
# Set up iurls/durls (shared across all agents)
=
=
# Merge with environment variables
= +
= +
= +
return
Important Distinction:
config[habName]["curls"]: Controller-specific endpoints (unique per agent)config["iurls"]: Shared introduction OOBIs (witnesses, watchers, mailboxes)config["durls"]: Shared data OOBIs (schemas, credentials)
Agent Configuration Writing
The agency writes agent-specific config files:
"""
Writes agent configuration as modified copy of agency config
"""
=
=
return
This creates a config file at ~/.keri/cf/{caid}.json containing:
Part 6: Endpoint Role Authorization at Runtime
Beyond file-based configuration, KERI uses endpoint role authorization messages for dynamic configuration.
Endpoint Role Authorization Messages
These are KERI events that authorize specific endpoints for specific roles:
# Controller endpoint
=
# Location scheme
=
Available Roles:
=
Available Schemes:
=
Adding Endpoint Roles with KLI
Instead of writing Python code, use KLI commands to add endpoint roles and locations:
Add a controller endpoint role:
Add the corresponding location:
Add multiple roles for the same endpoint:
# Agent role
# Mailbox role
# Location (shared by both roles)
Processing Endpoint Messages Programmatically
These messages are parsed into the database:
=
# Verify stored in database
=
assert # Role is authorized
=
assert == # Location stored
OOBI Resolution vs File Configuration
The key distinction:
File Configuration (curls, iurls, durls):
- Static configuration loaded at startup
- Processed during
Habery.makeHab()orreconfigure() - Stored in config file permanently
OOBI Resolution:
- Dynamic configuration at runtime
- Processed via
hab.db.oobis.pin()and OOBI resolution - Results in same endpoint role authorizations
Example:
# File-based
=
= # Processes curls
# Equivalent OOBI resolution
=
=
# ... OOBI resolver processes and creates endpoint records
Both methods result in the same database records:
hab.db.ends: Endpoint role authorizationshab.db.locs: Location schemes
Part 7: Answering the Original Questions
Now we can answer the Discord questions:
Q: "How to configure a controller/key store with iurls/curls like with kli init --file?"
Answer: Three methods exist:
Method 1: File-based initialization
# Create config file
=
=
# Initialize Habery with config
=
=
Method 2: Programmatic configuration
# Create Habery
=
# Create config
=
# Create hab (processes config)
=
Method 3: OOBI resolution
# Create hab
=
# Resolve OOBIs at runtime
=
=
# OOBI resolver processes in background
Method 4: KLI commands for endpoint roles
# Add controller endpoint role
# Add location
# Resolve witness OOBI
Q: "Is there a good example of running the controller server?"
Answer: KERIA provides the production pattern:
# Configure agency
=
# Create agency
=
# Setup HTTP servers and doers
=
# Run with Doist
=
For simpler use cases without HTTP servers:
# Just create the agency
=
# Create agents
=
# Run agency
=
Q: "No way for the controller to provide durls/iurls except resolving OOBIs?"
Answer: This is partially correct but requires clarification:
In KERIpy: Controllers can provide curls, iurls, and durls through:
- Configuration file (shown above)
- OOBI resolution at runtime
- Endpoint role authorization messages
In KERIA: The agency configuration provides global iurls and durls that are inherited by all agents. However:
- Limitation: Currently no API endpoint to add agent-specific
curls/iurls/durlsafter agent creation - Workaround: Resolve OOBIs through the
/oobisendpoint or KLI:
Via KERIA API:
{
}
Via KLI:
Both methods trigger OOBI resolution which creates the same endpoint records as file-based configuration.
Improvement Opportunity: As Daniel noted, adding an API to modify agent configuration would be valuable:
# Proposed API (not yet implemented)
{
}
Part 8: Well-Known OOBI Discovery
Well-known URIs provide standardized discovery endpoints for KERI resources following RFC 8615. GLEIF publishes production witness, AID, and schema OOBIs at https://gleif-it.github.io/.well-known/keri/oobi/.
Structure
The well-known directory follows a hierarchical structure:
/.well-known/
├── index.json # Main discovery index
├── schema.json # JSON Schema for validation
└── keri/oobi/
├── index.json # OOBI catalog (AIDs, witnesses, schemas)
├── schema.json # OOBI index schema
└── {identifier}/ # Per-identifier directories
└── index.json # Resource content
Discovery Protocol
Step 1: Discover available resources
Returns:
Step 2: Resolve specific identifier
# Get AID witness URLs
Returns KERI rpy message with witness URLs:
Resource Types
Well-known endpoints serve three resource types:
| Type | Prefix | Content | Content-Type |
|---|---|---|---|
| AID | E | KERI rpy message with witness URLs | application/cesr |
| Witness | B | Witness KEL (icp + rpy messages) | application/cesr |
| Schema | E | ACDC JSON Schema | application/cesr |
Configuration with wurls
Use well-known URIs in configuration files:
The wurls field is processed by Habery.reconfigure() similar to iurls and durls.
Implementation
The vLEI project includes a reference implementation in src/vlei/app/well_known.py:
=
=
The handler:
- Loads
index.jsonto categorize identifiers - Routes requests based on identifier type (AID/witness/schema)
- Returns appropriate content with correct content-type headers
Sample data from GLEIF's production well-known directory is available in vLEI/samples/oobis/.well-known/ including 3 AIDs, 10 witnesses, and 8 vLEI credential schemas.
Benefits
- Standardized Discovery: RFC 8615 compliant path structure
- No Authentication: Public discovery endpoints
- Complete Resource Sets: AIDs return all witness URLs in single response
- Schema Resolution: Direct access to ACDC schemas without OOBI resolution
- Production Ready: GLEIF operates this at scale for vLEI ecosystem
Part 9: Configuration Best Practices
For KERIpy Controllers
Use configuration files for static topology
- Witnesses that rarely change
- Well-known schema OOBIs
- Persistent peer relationships
Use OOBI resolution for dynamic discovery
- New witnesses discovered at runtime
- Peer identifiers learned through introductions
- Ephemeral connections
Use endpoint role authorization for services
- Your own controller endpoints
- Agent mailbox endpoints
- Service availability changes
For KERIA Agencies
Set global
iurlsin agency config- Standard witnesses all agents use
- Common watcher pools
- Shared schema registries
Keep
curlsagent-specific- Each agent may have unique controller endpoints
- Avoid global
curlsunless all agents share endpoints
Use configuration files for production
- Easier to manage than environment variables
- Version controlled configuration
- Clear separation of concerns
Provide OOBI resolution API for runtime changes
- New witnesses can be added without restart
- Dynamic peer discovery
- Schema updates
Conclusion
KERI configuration operates at multiple levels:
- Controller Configuration: File-based
curls,iurls,durlsor OOBI resolution - Witness Configuration: Endpoint role authorization for witness services
- Mailbox Configuration: Endpoint role authorization for agent/mailbox services
- Agency Configuration: Global KERIA service settings and OOBIs
- Agent Configuration: Per-controller settings derived from agency config
- Well-Known Discovery: RFC 8615 compliant
/.well-known/keri/oobi/endpoints for standardized resource discovery
Understanding these levels clarifies the apparent confusion about "no way to provide iurls except OOBIs." The truth is:
- KERIpy: Multiple methods exist (file, OOBI, endpoint messages, well-known URIs)
- KERIA: Agency provides global
iurls, agents inherit them, runtime updates via OOBI resolution
Both approaches ultimately create the same database records: endpoint role authorizations (db.ends) and location schemes (db.locs). The choice between file-based configuration, OOBI resolution, and well-known discovery depends on your use case: static infrastructure, dynamic topology, or public discovery.
For production deployments, use:
- Well-known URIs for public discovery of organizational infrastructure
- Agency config file for stable, shared witnesses and watchers
- OOBI resolution API for dynamic peer discovery and new witnesses
- Endpoint role messages for your own service availability
This provides the flexibility to handle static infrastructure, dynamic peer-to-peer discovery, and standardized public resource publication.