Skip to main content
Version: v5

Configuration Options

Quick reference for all harper-config.yaml top-level sections.

For how to apply configuration (YAML file, environment variables, CLI, Operations API), see Configuration Overview.


http​

Configures the Harper component server (HTTP, REST API, WebSocket). See HTTP Configuration for full details.

http:
port: 9926
securePort: 4443
cors: true
timeout: 120000
mtls: false
logging:
level: info
path: ~/hdb/log/http.log
  • sessionAffinity — Route requests from same client to same worker thread (ip or header name)
  • compressionThreshold — Response size threshold (bytes) for Brotli compression; 0 disables compression; Default: 0
  • cors — Enable CORS; Default: true
  • corsAccessList — Allowed domains for CORS requests
  • corsAccessControlAllowHeaders — Access-Control-Allow-Headers value for OPTIONS preflight
  • headersTimeout — Max wait for complete HTTP headers (ms); Default: 60000
  • maxHeaderSize — Max HTTP header size (bytes); Default: 16394
  • requestQueueLimit — Max estimated request queue time (ms) before 503; Default: 20000
  • keepAliveTimeout — Inactivity before closing keep-alive connection (ms); Default: 30000
  • port — HTTP port; Default: 9926
  • securePort — HTTPS port; requires TLS configuration; Default: null
  • http2 — Enable HTTP/2; Default: false (Added in: v4.5.0)
  • timeout — Request timeout (ms); Default: 120000
  • mtls — Enable mTLS authentication for incoming connections; sub-options: user, required, certificateVerification (see Certificate Verification)
  • logging — HTTP request logging (disabled by default, Added in: v4.6.0); sub-options: level, path, timing, headers, id. See Logging Configuration

threads​

Worker thread pool configuration.

threads:
count: 11
maxHeapMemory: 300
  • count — Number of worker threads; Default: CPU count minus one
  • maxHeapMemory — Heap limit per thread (MB). When unset, each thread's limit is derived from available memory and the number of HTTP worker threads, including isolated applications' threads
  • maxIsolated Added in: v5.3.0 — How many isolated applications may run, each in a dedicated worker thread; Default: 8. An isolated application past this limit is not loaded anywhere, and a deploy_component that would exceed it is refused with a 409. 0 admits none. Dedicated threads are added to count, not taken from it, so a node runs up to count + maxIsolated HTTP worker threads. When maxHeapMemory is unset, they all count when Harper divides available memory into each thread's default heap limit. Each one lowers that limit for threads started afterward, until it reaches its 512 MB floor, after which more threads add to the total heap the node may use
  • heapSnapshotNearLimit — Write a .heapsnapshot file when a thread nears its heap limit (loadable in Chrome DevTools Memory tab); Default: false. See Worker Thread Debugging
  • debug — Enable Node.js inspector; sub-options: port, startingPort, host, waitForDebugger. See Worker Thread Debugging
  • preload Added in: v5.2.0 — Module, or list of modules, to load via Node's --import before any Harper or application module on each worker thread. Intended for instrumentation and APM agents. Worker threads only (not under Bun).
  • preloadRequire Added in: v5.2.0 — The same, but via Node's --require (CommonJS). Worker threads only (not under Bun).
  • v8Flags Added in: v5.4.0 — V8 flag, or list of V8 flags, applied to the whole Harper process before its first worker thread starts. Each entry must start with --. Takes effect on process start. Not applied under Bun. See Applying V8 flags

Preloading an instrumentation agent​

An APM or instrumentation agent has to load before the code it instruments. These two keys put a module on each worker thread's startup, ahead of Harper's own modules and your application's.

The two keys differ in how the module is loaded, and that determines what it can do. --import evaluates the module as ESM, which is how an agent installs Node's module loader hooks so modules loaded later by import can be instrumented. --require runs the module's body, which is how an agent's initialization entry starts it.

Installing loader hooks and starting an agent are separate jobs, and which of an agent's entry points does which is specific to that agent. Some ship a single entry that does both; others split them across an --import entry and a --require entry. When they are split, set both keys.

Bare specifiers resolve against the node_modules of your installed components, so an agent can ship as a dependency of a deployed component. Absolute paths also work.

Follow your agent's own documentation for worker-thread setup, and confirm the telemetry you expect actually reaches your collector.

Example: a split-entry agent​

dd-trace is a split-entry agent, which makes it a useful illustration. On dd-trace 6.x, dd-trace/register.js installs the ESM loader hooks but never calls init(), so preload on its own leaves the tracer uninitialized — it still hands out spans with plausible trace ids, but they are no-ops and nothing is exported. dd-trace/init is the entry that calls init(). Setting both keys covers both jobs:

threads:
preloadRequire: dd-trace/init # the entry that calls init()
preload: dd-trace/register.js # ESM loader hooks for automatic instrumentation

dd-trace/initialize.mjs looks like a single-entry shortcut around that pairing, but it is not one on a worker thread: it gates both its init() call and its loader-hook registration behind isMainThread, so under --import on a worker it starts nothing and registers nothing.

An agent that is one of several preloaded modules still needs its own pairing:

threads:
preloadRequire: dd-trace/init # the entry that calls init()
preload:
- dd-trace/register.js
- /opt/instrumentation/agent.mjs

The dd-trace behavior described here was observed on 6.x. Check your agent's current documentation, and confirm your spans arrive at your collector.

Applying V8 flags​

Some V8 flags, such as --optimize-for-size, cannot be passed through NODE_OPTIONS (Node refuses them there) or to a worker thread. v8Flags applies them inside Harper instead: the main thread calls Node's v8.setFlagsFromString() for each entry before the first worker thread starts, so every worker thread runs with them. V8 flags are process-wide, so they also affect the main thread from that point on. The main thread's isolate already exists, though, so flags V8 reads only when an isolate is created (such as heap sizing) do not change it. With count: 0, the main thread serves requests, so those creation-time flags do not affect request handling; flags V8 reads while running still apply.

threads:
v8Flags:
- --optimize-for-size
- --max-semi-space-size=1

The value can also be set from the environment, for example HARPER_SET_CONFIG='{"threads":{"v8Flags":["--optimize-for-size","--max-semi-space-size=1"]}}'.

  • Validation. Surrounding whitespace is trimmed, and an empty entry is ignored. An entry that does not start with -- is refused by set_configuration, and stops Harper at startup with an error naming threads.v8Flags. V8 skips a flag it does not recognize and only prints Error: unrecognized flag to standard error, so check the spelling against your Node version (node --v8-options).
  • Implied flags are not applied. On the command line, some flags turn on others; --optimize-for-size also caps the young-generation semi-space at 1 MB. Applied at runtime, a flag does not do that, so list the implied flags explicitly, as the example does with --max-semi-space-size=1.
  • When it takes effect. Flags are read once per process. A changed value takes effect when the whole process restarts: the restart operation, which restarts only the node that receives it, or a process or container restart. restart_service restarts only worker threads, which keep the process's flags, so it does not apply a change, even with "replicated": true. To roll back, remove the setting from HARPER_SET_CONFIG or HARPER_CONFIG if it came from there, and from harper-config.yaml (for example set_configuration with "threads_v8Flags": null), then restart the process. A THREADS_V8FLAGS environment variable is written into harper-config.yaml at every startup, so unset it as well; unsetting the variable alone leaves the flags in the file.
  • Expert setting. Flags change V8's behavior for the whole process and are not validated beyond the -- prefix. A conflicting combination can stop the process at startup. Measure memory and throughput with your workload before relying on a flag in production.

authentication​

Authentication and session configuration. Added in: v4.1.0; enableSessions added in v4.2.0. See Authentication Configuration.

authentication:
authorizeLocal: true
cacheTTL: 30000
enableSessions: true
operationTokenTimeout: 1d
refreshTokenTimeout: 30d
  • authorizeLocal — Auto-authorize loopback requests as superuser; Default: true
  • cacheTTL — Session cache duration (ms); Default: 30000
  • enableSessions — Cookie-based sessions; Default: true
  • operationTokenTimeout — Access token lifetime; Default: 1d
  • refreshTokenTimeout — Refresh token lifetime; Default: 30d
  • logging — Authentication event logging (Added in: v4.6.0); sub-options: path, level, tag, stdStreams. See Logging Configuration

operationsApi​

Harper Operations API endpoint configuration. See Operations API Overview.

operationsApi:
network:
port: 9925
cors: true
tls:
certificate: ~/hdb/keys/certificate.pem
privateKey: ~/hdb/keys/privateKey.pem
  • componentFile.maxSize — Maximum file size in bytes returned by get_component_file. Requests for files larger than this limit are rejected with HTTP 413. Default: 5242880 (5 MB)
  • network.cors / network.corsAccessList — CORS settings
  • network.domainSocket — Unix socket path for CLI communication; Default: <rootPath>/hdb/operations-server
  • network.headersTimeout / network.keepAliveTimeout / network.timeout — Timeout settings (ms)
  • network.port — Operations API port; Default: 9925
  • network.securePort — HTTPS port; Default: null
  • tls — TLS override for the Operations API; sub-options: certificate, certificateAuthority, privateKey. See tls

tls​

Global TLS configuration for HTTPS and TLS sockets (used by HTTP and MQTT). Can be a single object or an array for SNI. See TLS and Certificate Management.

tls:
certificate: ~/hdb/keys/certificate.pem
certificateAuthority: ~/hdb/keys/ca.pem
privateKey: ~/hdb/keys/privateKey.pem
  • certificate — Path to TLS certificate; Default: <rootPath>/keys/certificate.pem
  • certificateAuthority — Path to CA file; Default: <rootPath>/keys/ca.pem
  • privateKey — Path to private key; Default: <rootPath>/keys/privateKey.pem
  • ciphers — Allowed TLS cipher suites
  • unixDomainSockets Added in: v5.1.0 — true makes each HTTP worker thread also listen on a Unix domain socket under <rootPath>/sockets for each secure port, for a proxy on the same host to forward to. Isolated applications are reachable only this way, so they require it; Default: false

With tls as a single object, put unixDomainSockets: true inside it. With an SNI certificate array, put tls_unixDomainSockets: true at the root of harper-config.yaml, alongside tls; placing unixDomainSockets inside an array entry does not enable the global setting. On v5.3.0, a standalone TLS_UNIXDOMAINSOCKETS=true environment variable does not enable this SNI configuration; use the root YAML setting:

tls_unixDomainSockets: true
tls:
- host: app.example.com
certificate: ~/hdb/keys/app-certificate.pem
privateKey: ~/hdb/keys/app-private-key.pem

mqtt​

MQTT protocol configuration. Added in: v4.2.0. See MQTT Configuration.

mqtt:
network:
port: 1883
securePort: 8883
webSocket: true
requireAuthentication: true
  • network.port — Insecure MQTT port; Default: 1883
  • network.securePort — Secure MQTT port; Default: 8883
  • network.mtls — Enable mTLS for MQTT connections; sub-options: user, required, certificateAuthority, certificateVerification
  • webSocket — Enable MQTT over WebSocket on HTTP port; Default: true
  • requireAuthentication — Require credentials or mTLS; Default: true
  • logging — MQTT event logging (Added in: v4.6.0); sub-options: path, level, tag, stdStreams. See Logging Configuration

logging​

Application logging. Added in: v4.1.0; per-component logging added in v4.6.0. See Logging Configuration.

logging:
level: warn
root: ~/hdb/log
stdStreams: false
auditLog: false
rotation:
interval: 1D
maxSize: 100M
  • level — Log verbosity (trace → debug → info → warn → error → fatal → notify); Default: warn
  • file — Write to file; Default: true
  • root — Log directory; Default: <rootPath>/log
  • path — Explicit log file path (overrides root)
  • stdStreams — Write to stdout/stderr; Default: false
  • console — Include console.* output; Default: true
  • auditLog — Enable table transaction audit logging; Default: false
  • auditRetention — Audit log retention duration; Default: 3d
  • external — Logging for components using the logger API; sub-options: level, path
  • rotation.enabled / rotation.compress / rotation.interval / rotation.maxSize / rotation.path — Log file rotation (activates when interval or maxSize is set)
  • auditAuthEvents.logFailed / auditAuthEvents.logSuccessful — Log failed/successful authentication events; Default: false

node​

This node's identity within the cluster. See Replication.

node:
hostname: server-one
  • hostname Changed in: v5.3.0 — This node's identity: it becomes the node's TLS certificate common name and the host that replication advertises to peers and dials to reach this node. Must be a bare hostname or IP literal (e.g. server-one, 10.0.0.5, or the unbracketed IPv6 form ::1) — not a URL and not host:port. Harper fails to start if the value carries a scheme, port, path, credentials, query string, or fragment, is a bracketed IPv6 literal ([::1]), or is not a string; the startup error names the offending value and the reason. Earlier versions accepted such values and silently corrupted certificate matching and replication — a node configured as http://host:9926 advertised and dialed a host literally named http. The same requirement applies to replication.hostname. If unset, Harper uses the first valid bare host among replication.hostname, the host in replication.url, the TLS certificate common name, and the Operations API host, falling back to 127.0.0.1.

replication​

Native WebSocket-based replication (Plexus). Added in: v4.4.0. See Replication and Clustering.

replication:
hostname: server-one
url: wss://server-one:9933
databases: '*'
routes:
- wss://server-two:9933
  • hostname Changed in: v5.3.0 — This instance's hostname within the cluster. Subject to the same bare hostname or IP literal requirement as node.hostname — a URL, host:port, or non-string value fails startup. When both are set, node.hostname wins.
  • url — WebSocket URL peers use to connect to this instance
  • databases — Databases to replicate; Default: "*" (all). Each entry supports name and sharded
  • routes — Peer nodes; URL strings or {hostname, port, startTime, revokedCertificates} objects
  • port — Replication port
  • securePort — Secure replication port; Default: 9933 (changed from 9925 in v4.5.0)
  • threads — Worker threads dedicated to replication, which then own the replication port and every subscription and load no application code; requires port or securePort; see Dedicated Replication Threads; Default: 0 (replication runs on the HTTP worker threads) (Added in: v5.4.0)
  • enableRootCAs — Verify against Node.js Mozilla CA store; Default: true
  • blobTimeout — Blob transfer timeout (ms); Default: 120000
  • blobGapReconnectMs — Interval (ms) for the blob-gap watchdog: when a transient blob save failure pins a replication resume cursor, the connection is forced to reconnect on this cadence so the gapped blob is re-streamed and, during a bulk copy, the copy resumes from the last banked cursor. Lower values heal gaps faster at the cost of more reconnects on a link whose faults never heal; Default: the blobTimeout value
  • copyCursorFlushBytes — Bytes of applied bulk-copy data between durable flushes of the copy resume cursor (RocksDB); Default: 67108864
  • copyCursorFlushIntervalMs — Maximum time (ms) between durable flushes of the bulk-copy resume cursor (RocksDB); Default: 5000
  • blobSendDrainTimeout — Maximum time (ms) a worker waits for in-flight replication blob sends to finish before shutting down during a restart, so a rolling restart (e.g., a component deploy reload) doesn't interrupt a transfer in progress. Only sends that are still making progress are waited on; 0 disables draining; Default: 600000
  • failOver — Failover to alternate node if peer unreachable; Default: true
  • shard — Shard ID for traffic routing; see Sharding
  • mtls.certificateVerification — Certificate revocation checking (CRL/OCSP) for replication connections; see Certificate Verification
  • logging — Replication event logging; sub-options: path, level, tag, stdStreams. See Logging Configuration

storage​

Database storage configuration. See Storage Tuning for guidance on tuning these options for production workloads, Database Overview for general database concepts, and Compaction for reclaiming space inside existing files.

storage:
path: ~/hdb/database
caching: true
compression: true
compactOnStart: false
engine: rocksdb
  • engine — The database storage engine to use for new databases. Currently supported engines are rocksdb and lmdb. The default is rocksdb. Existing databases will use the engine that was used when they were created.
  • writeAsync — Disable disk sync for higher throughput (disables durability guarantees); Default: false
  • caching — In-memory record caching; Default: true
  • compression — LZ4 record compression; Default: true (enabled by default since v4.3.0). Sub-options: dictionary, threshold
  • compactOnStart — Compact all non-system databases on startup; Default: false (Added in: v4.3.0)
  • compactOnStartKeepBackup — Retain compaction backups; Default: false
  • maxTransactionQueueTime — Per-thread write-commit duration threshold for HTTP 503 backpressure; see Storage Tuning; Default: 45s
  • noReadAhead — Advise OS against read-ahead; Default: false
  • prefetchWrites — Prefetch before write transactions; Default: true
  • randomAccessFields Added in: v5.1.0 — Encode records as typed random-access structures, so a single field can be read without decoding the whole record. Best for tables whose records have a stable set of fields with stable types; leave off for wide, sparse, or variably typed schemas. Read by each table when its store opens, so a change applies to existing tables on restart, and ignored entirely by tables declaring the @table(randomAccessFields:) directive; Default: false. See Storage Tuning — Record Encoding
  • path — Database files directory; Default: <rootPath>/database
  • backupPath — Directory for managed database backups (created by create_backup), one subdirectory per database; Default: <rootPath>/backup (Added in: v5.2.0)
  • blobPaths — Blob storage directory or directories; Default: <rootPath>/blobs (Added in: v4.5.0)
  • pageSize — Database page size (bytes); Default: OS default
  • reclamation.threshold — Free-space ratio below which reclamation begins evicting from caching tables; Default: 0.4 (Added in: v4.5.0)
  • reclamation.interval — Free-space check interval; Default: 1h
  • reclamation.evictionFactor — Heuristic factor for early eviction under disk pressure; Default: 100000. See Storage Tuning — Reclamation
  • rocks.blockCacheSize — RocksDB shared block cache size in bytes; Default: 25% of constrained memory. See Storage Tuning — RocksDB Memory (Added in: v5.1.0)
  • rocks.writeBufferManagerSize — Process-wide cap (bytes) on RocksDB memtable memory across all databases. 0 disables; Default: one third of blockCacheSize (enabled). See Storage Tuning — RocksDB Memory (Added in: v5.1.0)
  • rocks.writeBufferManagerCostToCache — When enabled, memtable memory and the block cache share a single, unified memory pool. During heavy write bursts, the block cache dynamically shrinks to give memtables more room. Once those memtables flush to disk, the block cache automatically reclaims that memory space. Default: true. Has no effect when writeBufferManagerSize is 0. (Added in: v5.1.0)
  • rocks.writeBufferManagerAllowStall — Stall writes when memtable memory exceeds writeBufferManagerSize (hard cap) instead of allowing brief overshoot with more aggressive flushing (soft cap); Default: true. (Added in: v5.1.0)

databases​

Per-database and per-table file path overrides. Must be set before the database/table is created. See Database Overview.

databases:
myDatabase:
path: /data/myDatabase
auditPath: /data/myDatabase-audit
tables:
myTable:
path: /data/myTable
  • <dbName>.path — Database files directory
  • <dbName>.auditPath — Audit log directory for this database
  • <dbName>.tables.<tableName>.path — Table files directory

analytics​

Analytics aggregation configuration. See Analytics Overview.

analytics:
aggregatePeriod: 60
replicate: false
  • aggregatePeriod — Aggregation interval (seconds); Default: 60 (Added in: v4.5.0)
  • storageInterval — Aggregation cycles between storage-volume measurements (0 disables); Default: 10
  • replicate — Replicate analytics data across cluster; Default: false
  • logging — Per-subsystem logger override for analytics writes. See Logging Configuration

localStudio​

Local Harper Studio GUI. See Studio.

localStudio:
enabled: true
  • enabled — Enable local Studio at http://localhost:<port>; Default: false

componentsRoot​

Path to local component files. Added in: v4.2.0 (previously customFunctionsRoot). See Components.

componentsRoot: ~/hdb/components

rootPath​

Root directory for all Harper persistent data, config, logs, and components.

rootPath: /var/lib/harper

agent​

Added in: v5.2.0

Built-in Harper agent — an LLM loop that operates this instance through Harper's own operations, scoped filesystem access, followup scheduling, the V8 inspector, and outbound HTTP. Disabled by default, so it never incurs LLM cost unless you turn it on. Driven through the Agent operations, which also describe the privilege boundary before you enable it.

agent:
enabled: true
model: default
maxTurns: 50
maxTokens: 16384
maxToolResultBytes: 65536
autoApprove: false
allowDestructive: false
user: hdb_agent
  • enabled — Enable the agent component; Default: false
  • provider — Recorded on the session but not yet used to route the model call — only model reaches the provider. Set the provider through the models configuration instead
  • model — Model id override, passed through to the model call; Default: the models generative default
  • maxTurns — Maximum tool-call iterations in a single run; Default: 50
  • maxTokens Added in: v5.4.0 — Output-token cap sent with every model request the agent makes. A reply cut off at this cap ends the run with status error, not completed, and lastError says it was cut off; raise the cap if replies keep hitting it. A cut-off reply can also mean the session's transcript filled the model's context window, which a higher cap does not fix; start a new session instead. Keep it within the model's own output limit: most providers reject a larger value on every request, and the run then ends error with the provider's message, so lower it for a model with a small output limit (some older models allow 4096 or 8192). Before v5.4.0 the agent sent no cap: Anthropic models, called directly or through Bedrock, then used 4096, and the other backends sent none; Default: 16384
  • maxToolResultBytes Added in: v5.4.0 — Largest tool result, in bytes of JSON, the agent adds to its conversation. A larger result is cut to this size, ending with a note that gives the original size and tells the model how to ask for less. The file-reading tools return pages of at most half this size, measured as JSON, so a page arrives whole unless the file's path alone takes up most of the cap. Lower it for a model with a small context window. It must be an integer from 1024 to 1048576; at startup an invalid value is logged and the default is used. See When a conversation outgrows the model's context window; Default: 65536
  • maxCostUsd — Intended per-run cost ceiling. Not enforced — it is a stored setting only, and nothing checks spend against it; Default: 5.00
  • autoApprove — Run without per-action approval gates; Default: false
  • allowDestructive — Include the tools marked destructive in the agent's toolset: write_file, the inspector's code-evaluation tools, and any operations tool carrying MCP's destructiveHint (drop_table, delete, restart, set_configuration, ...). When false they are removed entirely rather than gated. That hint comes from a curated set in core which does not cover every damaging operation, so this is not a complete safety boundary on its own — see Agent operations; Default: false
  • user — Harper user the agent's operations tools run as; the filesystem, HTTP, schedule, and inspector tools always run at process privilege regardless. If it cannot be resolved and it is not the default, the agent fails closed and runs with no operations tools; Default: hdb_agent, which falls back to a super_user bootstrap identity
  • componentsScope — The agent's components filesystem scope, which it reads and, with allowDestructive, writes; absolute or relative to rootPath. Read at startup only; Default: the full componentsRoot
  • configScope Added in: v5.4.0 — The agent's read-only config filesystem scope: a file or a directory, absolute or relative to rootPath. Read at startup only. See Filesystem scopes; Default: the Harper config file only
  • httpFetch Added in: v5.4.0 — Whether the agent has its http_fetch tool, and which hosts it may reach: true, false, or { allow: [...] }. Read at startup only. See Restricting http_fetch; Default: true
  • systemPromptAppend — Operator text appended to the agent's system prompt

enabled, provider, model, maxTurns, maxTokens, maxToolResultBytes, maxCostUsd, autoApprove, allowDestructive, and systemPromptAppend can also be changed at runtime with set_agent_config, which applies in memory only. enabled is the exception worth knowing: it cannot switch the agent on, because with the agent disabled at startup no agent operation is registered at all. httpFetch, componentsScope, and configScope are read at startup only, and set_agent_config rejects them.

Filesystem scopes​

Changed in: v5.4.0

The agent's read tools (read_file, list_dir, grep_files, and tail_file) take a root naming one of three scopes. write_file, available when allowDestructive is on, takes no root and always writes to components.

ScopeReachesAccess
componentscomponentsRoot, or componentsScope when setread and write
logsthe log directoryread
configthe Harper config file, or configScope when setread

Before v5.4.0, config was the directory holding harper-config.yaml. On a default install that is rootPath itself, so the agent could read keys/, database/, and the rest of the root. It is now the config file alone, and list_dir shows only that file.

To point the agent somewhere else, such as a directory of extra configuration, set configScope to a file or a directory, absolute or relative to rootPath. It replaces the config file as the scope: the config file stays readable only if the new target contains it, as rootPath does. It is read at startup only. A path that names no file or directory at startup leaves the config scope unavailable and logs an error.

agent:
enabled: true
configScope: /etc/harper/extra

Whatever componentsScope and configScope say, every scope refuses:

  • Harper's key directories, <rootPath>/keys (TLS and JWT keys) and <rootPath>/ssh (git deploy keys): nothing in them is read, listed, or written. Paths are compared after resolving symlinks.
  • Key file names, *.pem, *.key, and .jwtPass: not read. list_dir still shows the names, and write_file can still create such a file outside the key directories.
  • Text holding a PEM private key (-----BEGIN ... PRIVATE KEY-----), such as an inline tls.privateKey in the config file: read_file refuses a page that holds one or starts partway through one (it looks back 128 KiB for the BEGIN line, more than any PEM key spans, post-quantum ones included), tail_file refuses lines that hold one or a file that ends partway through one, and grep_files skips the file.

A key with neither a key file name nor PEM armor, such as a .p12 bundle or raw DER, is not recognized outside the key directories.

The config file can still hold secrets that are not PEM keys, such as a model apiKey or storage credentials, and the agent reads it. Setting configScope to rootPath also restores read access to the raw database files and backups.

Restricting http_fetch​

Added in: v5.4.0

http_fetch lets the agent send HTTP requests from the Harper process: to its own REST endpoints while it builds an app, or to pull a reference page. The same agent reads table rows, logs, and component source, and text in any of them can steer it, so an enabled agent has a read path and an egress path in one toolset. agent.httpFetch bounds that tool. It is not a bound on every way the agent can reach the network: with allowDestructive on, for example, the agent can write component code, and the Harper process runs that code.

Remove the tool when the agent has no reason to reach the network, for example an investigation or operations-assistant role:

agent:
enabled: true
httpFetch: false

Or keep it and allow only named hosts:

agent:
enabled: true
httpFetch:
allow:
- localhost:9926
- '*.example.com'
ValueEffect
true (default)The tool is available. Cloud-metadata hosts and the IPv4 link-local range are refused.
falseThe tool is removed from the agent's toolset. The model never sees it, and the system prompt stops suggesting HTTP checks.
{ allow: [...] }The tool is available, and a request is refused unless its host matches an entry. Its description lists the entries, so the model knows where it can go.

Allow-list entries take these forms:

  • api.example.com — that host, on any port
  • localhost:9926 — that host on that port only. A URL without a port uses 80 for http and 443 for https.
  • *.example.com — any subdomain, at any depth. It does not match example.com itself; list that separately.
  • [::1] or [::1]:9926 — an IPv6 address, in brackets

Entries and request URLs are compared in canonical form, so letter case, internationalized names, and shorthand spellings of an IPv4 address all match the same entry. An IPv4 entry does not admit the IPv4-mapped IPv6 form of its address ([::ffff:...]); list that separately if you need it.

Some behavior worth knowing:

  • Every redirect hop is checked. A redirect to a host the policy refuses fails the request before that host is contacted. This applies in every mode, so with the default true, a redirect into the cloud-metadata range fails too.
  • The metadata block comes first. Listing a cloud-metadata or link-local address does not make it reachable.
  • Matching is by host name, not address. An allowed name whose DNS resolves to an internal address is still reached. List names whose DNS you control.
  • An empty list removes the tool, the same as false. A value Harper cannot read also removes the tool, and logs an error naming it, while the rest of the agent stays available.
  • It is read at startup only. Set it in harper-config.yaml, with set_configuration, through HARPER_CONFIG, or as an environment variable or CLI argument (AGENT_HTTPFETCH=false, or AGENT_HTTPFETCH_ALLOW='["localhost:9926"]' as a JSON array), then restart. A comma-separated string is not a list: it is rejected, and the tool is removed. set_agent_config rejects it at runtime. Because set_configuration can write it and restart applies it, the policy only holds against the agent itself while those two operations stay out of its toolset (they are not in the default operations allow list) or behind approval.

deployment​

Retention for what deploy_component leaves behind, and how long startup waits for component installs. See deploy_component.

deployment:
startupInstallTimeout: 600000
payloadRetention:
maxSize: 10485760
stagingRetention:
maxCount: 5
  • startupInstallTimeout Added in: v5.3.0 — Milliseconds. The longest startup waits for component installs before it opens listeners. While waiting, it logs every 60 seconds which ones remain outstanding — including throughout 0's unbounded wait — unless every install settles first, which always ends the wait before that first log, whatever the timeout; a failed install logs immediately regardless of the timeout. A component whose install is still running at the deadline keeps installing in the background, and the node serves the version already installed meanwhile, or nothing for a component that was never installed. When that install succeeds, the swap lands on disk immediately and Harper flags a restart as needed (restartRequired in get_status called with no id) — any one component's late success sets it, even while others are still installing. Code already loaded keeps running until that restart, but some component plugins apply an added or changed file on their own rather than waiting for one; content served straight from disk, such as static files, can also reflect the new version before the restart. Outside harper dev, which restarts automatically soon after any restart request, Harper does not restart on its own. An install that fails after the deadline does not set the flag; Harper only logs an error naming the component. 0 waits indefinitely, which was the behavior before v5.3.0. An empty value uses the default. A negative, non-finite, or otherwise unparseable value also falls back to it, with a warning in the log; Default: 600000 (10 minutes)
  • payloadRetention.maxSize Added in: v5.1.15 — Bytes. After a successful deploy, a payload larger than this has its stored tarball (payload_blob) dropped from the hdb_deployment row; the row and its metadata stay. Set it very high to retain every payload; Default: 10485760 (10 MiB)
  • stagingRetention.maxCount Added in: v5.3.0 — How many complete builds that are not live may remain per component under <componentsRoot>/.deploy-staging: builds staged with activate: false, and releases replaced by a later deploy, which deployment_id can make live again. The newest survive; older ones are removed when that component next deploys and at startup. drop_component removes all of them. Each is a full installed copy of the component, node_modules included. 0 keeps none, and no replaced release is kept. This bounds dormant builds only, not a disk quota: staging directories that belong to an in-flight or unsettled deploy are never touched; Default: 5

applications​

Added in: v5.0.0

Controls how application modules are loaded and isolated. See Module Loading for the full reference.

applications:
lockdown: freeze-after-load
moduleLoader: vm-current-context
dependencyLoader: auto
allowedDirectory: app
allowedSpawnCommands:
- npm
- node
# allowedBuiltInModules: [] # if omitted, all Node.js built-ins are allowed
  • lockdown — Indicates if intrinsic/built-in objects should be locked down/frozen. This provides additional security and protection against prototype pollution attacks. The default is freeze-after-load (since v5.0.2; v5.0.0 and v5.0.1 defaulted to freeze), which freezes the important built-in objects once all components have loaded, so component initialization can still modify them. This can also be set to freeze (freeze before any application code loads), none, or ses (lockdown provided by the ses package, which is more strict). See Intrinsic Lockdown.
  • moduleLoader — The method used to load modules (and isolate the application). The default is vm-current-context (since v5.1.0; v5.0.x defaulted to vm), which uses Node's VM module loader in Harper's own context so applications share JavaScript intrinsics. This can also be set to vm (VM loader with a separate context and its own intrinsics per application), native (standard Node module loader), or compartment, which uses the ses implementation of the proposed Compartment functionality. See Module Loader Modes.
  • dependencyLoader — The application module loader can be used to load packages/dependencies (installed as dependencies from the package.json). The default is 'auto', which only use the VM module loader if the package specifies harper as a dependency. This can also be set to app to always use the application module loader or native to always native module loader for packages. See Dependency Loading.
  • allowedDirectory — Added in v5.0.4. Restricts where an application may load modules from. The default is app, which permits only modules within the application's own directory tree; loading from outside it throws. Set to any to remove the restriction. Dev-mode installs default to any. Applies to imports the application module loader handles, so treat it as a configuration guardrail rather than a security boundary. See Allowed Directory.
  • allowedBuiltInModules — An allowlist of the Node.js built-in modules applications may import. If omitted, all built-ins are allowed. Matching strips a node: prefix and compares the first path segment, so fs also permits node:fs/promises. Like allowedDirectory, it applies to imports the application module loader handles. See Allowed Built-in Modules.
  • allowedSpawnCommands - This lists the specific commands that can be spawned by the application (using child_process's spawn() and execFile() functions). You can add commands that your application will need to launch (this is to protect against malicious code spawning processes). Only the first token of the command is matched, spawning also requires a mandatory name option, and the call is subject to a node-wide single-process lock — see Child Processes for the full contract.

Component Configuration​

Installed components are configured directly at the root of harper-config.yaml using the component name as the key — not nested under a components: section. See Components.

my-component:
package: 'HarperDB-Add-Ons/my-component'
port: 4321
  • <component-name>.package — NPM package name, GitHub repo (user/repo), or local path
  • <component-name>.port — Port for the component; Default: value of http.port