Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.oktolabs.ai/llms.txt

Use this file to discover all available pages before exploring further.

Environment variables

Pulse reads configuration from environment variables and an optional .env file via pydantic-settings. The base settings class is CoreSettings (okto-pulse-core/src/okto_pulse/core/infra/config.py:22); the community edition extends it with CommunitySettings (okto-pulse/src/okto_pulse/community/config.py:7). Both use:
model_config = SettingsConfigDict(
    env_file=".env",
    env_file_encoding="utf-8",
    case_sensitive=False,
    extra="ignore",
)
Three rules follow from that config:
  1. Field name → env var name. Every field on CoreSettings and CommunitySettings accepts an env var equal to the field name uppercased. data_dirDATA_DIR, kg_base_dirKG_BASE_DIR, etc.
  2. Case-insensitive. DATA_DIR, data_dir, and Data_Dir are equivalent. We use uppercase throughout this page by convention.
  3. .env is loaded automatically from the working directory of okto-pulse serve. Useful for local development; production should set vars in the process environment.
A small set of variables outside CoreSettings are read directly by the CLI, the MCP server, or the Docker image — they are called out explicitly in their sections below. For runtime tuning rationale, see Server configuration. For the on-disk layout these variables control, see Storage paths. Source-of-truth for the table below is 80-pulse-feature-inventory.md:178–211.

CLI runtime control

These variables gate the CLI itself — they are read by okto-pulse serve and friends, not by CoreSettings.
Env varDefaultRange / typePurpose
OKTO_PULSE_PORT8100intAPI + Web UI port. Set by cmd_serve from --api-port before importing okto_pulse.community.main. (community/cli.py:244, inventory:181)
OKTO_PULSE_MCP_PORT8101intMCP server port. Set by cmd_serve from --mcp-port. (community/cli.py:245, inventory:182)
OKTO_PULSE_TERMS_ACCEPTEDunset1 to pre-acceptPre-accept the Terms of Use; equivalent to --accept-terms. Triggers write_acceptance("env"). (community/cli.py:252, acceptance.py:29, inventory:183)
OKTO_PULSE_NO_BANNERunsetany non-emptySuppress the ASCII banner on startup. Useful for JSON pipes. (community/cli.py:47, inventory:184)
OKTO_PULSE_HOME~/.okto-pulsepathOverrides the base directory used for persisted Terms of Use acceptance state. Source: community/acceptance.py:23.
OKTO_PULSE_STARTUP_TIMEOUT_SECONDS120float seconds, min 1Timeout while waiting for the paired API/MCP uvicorn listeners to report startup. Source: community/main.py:62.
OKTO_PULSE_STARTUP_TIMEOUTunsetfloat seconds, min 1Legacy alias for OKTO_PULSE_STARTUP_TIMEOUT_SECONDS; read only when the _SECONDS variable is absent. Source: community/main.py:63.
OKTO_PULSE_SHUTDOWN_TIMEOUT_SECONDS5float seconds, min 1Timeout for graceful shutdown of the paired API/MCP servers. Source: community/main.py:88.
OKTO_PULSE_SHUTDOWN_TIMEOUTunsetfloat seconds, min 1Legacy alias for OKTO_PULSE_SHUTDOWN_TIMEOUT_SECONDS; read only when the _SECONDS variable is absent. Source: community/main.py:89.
OKTO_PULSE_PORT and OKTO_PULSE_MCP_PORT must be set before okto_pulse.community.main is imported — the module evaluates app = create_community_app() at import time and bakes the values into the Web UI’s /config.js. The CLI does this for you; if you embed Pulse in your own Python process, set both before any import okto_pulse.community.main.

Server

These map to CoreSettings fields (config.py:22–60, inventory:751–765).
Env varDefaultFieldNotes
HOST127.0.0.1 (community) / 0.0.0.0 (Docker)hostAPI + Web UI bind. Loopback by default; Docker images set 0.0.0.0 so port forwards reach uvicorn. (CLAUDE.md “Env vars the runtime reads”, inventory:761)
MCP_HOST127.0.0.1 (community) / 0.0.0.0 (Docker)runtimeMCP uvicorn bind. Honored in both community/main.py (since v0.1.12) and core/mcp/server.py:run_mcp_server. Without it, MCP binds to 127.0.0.1 even with -p 8101:8101.
PORTunsetfallbackRead before OKTO_PULSE_PORT in community/main.py:652. Useful for PaaS platforms (Heroku, Fly) that inject PORT.
MCP_PORTunsetfallbackRead before OKTO_PULSE_MCP_PORT in community/main.py:655 and in core/mcp/server.py:12852.
ENVIRONMENTdevelopmentenvironmentFree-form label. Surfaced in startup banner and /health. (inventory:754)
DEBUGfalsedebugRaises okto_pulse.* loggers from INFO to DEBUG; surfaces SQLAlchemy echo. (inventory:753)
CORS_ORIGINS* (community) / http://localhost:5173,http://localhost:3000 (core)cors_originsComma-separated allowed origins. The community edition’s _derive_paths validator overrides core to * so the bundled UI works regardless of port. (community/config.py:33, inventory:765)
PUBLIC_HOST127.0.0.1hostname/IPHost value injected into /config.js for browser-side API and MCP URLs. Source: community/main.py:304.
PUBLIC_API_PORTactive API portintAPI port injected into /config.js; use when a reverse proxy or container publish port differs from the internal listener. Source: community/main.py:305.
PUBLIC_MCP_PORTactive MCP portintMCP port injected into /config.js; use when the browser should connect through a published/proxied MCP port. Source: community/main.py:306.
export ENVIRONMENT=staging
export DEBUG=true
export CORS_ORIGINS="http://localhost:5173,https://pulse.staging.example.com"
okto-pulse serve --accept-terms
INFO  okto_pulse.api  CORS allow_origins=['http://localhost:5173', 'https://pulse.staging.example.com']
DEBUG sqlalchemy.engine  BEGIN (implicit)
See Server configuration for ports, bind hosts, CORS, and logging in depth.

Storage

These map to CoreSettings and CommunitySettings fields (inventory:185–189).
Env varDefaultFieldNotes
DATA_DIR~/.okto-pulseCommunitySettings.data_dirSingle root for SQLite, attachments, and KG graph files. The community edition’s _derive_paths validator rebases DATABASE_URL and UPLOAD_DIR under it. (community/config.py:11)
KG_BASE_DIR~/.okto-pulsekg_base_dirKG-only root; relocates per-board boards/{id}/graph.lbug and global/discovery.lbug independently of DATA_DIR. Defaults to DATA_DIR in the Docker image (KG_BASE_DIR=/data). (inventory:189)
DATABASE_URLsqlite+aiosqlite:///{data_dir}/data/pulse.dbdatabase_urlSQLAlchemy connection string. Override to point at an alternate SQLite path; non-SQLite backends are not supported in the community edition. (inventory:185)
UPLOAD_DIR{data_dir}/uploadsupload_dirCard attachment storage. (inventory:186)
MAX_UPLOAD_SIZE10485760 (10 MB)max_upload_sizePer-attachment cap in bytes. (inventory:187)
# Move the entire data root onto a faster volume
export DATA_DIR=/srv/pulse
export KG_BASE_DIR=/srv/pulse
okto-pulse serve --accept-terms
$ tree -L 1 /srv/pulse
/srv/pulse
├── boards
├── data
├── global
└── uploads
See Storage paths for the full directory tree, the unprefixed Docker volume gotcha, and backup/migration recipes.

Knowledge graph — embeddings

Configures the embedding backend used for KG semantic search. (inventory:191–193)
Env varDefaultFieldNotes
KG_EMBEDDING_MODEsentence-transformers (community) / stub (core)kg_embedding_modeEither sentence-transformers (real ~90 MB MiniLM model) or stub (deterministic hash, no model). The community edition’s CommunitySettings overrides core’s default to sentence-transformers.
KG_EMBEDDING_MODELsentence-transformers/all-MiniLM-L6-v2kg_embedding_modelHugging Face repo ID. Pulse ships with the model pre-baked into the Docker image at /opt/hf-cache.
KG_EMBEDDING_DIM384kg_embedding_dimVector dimension. Must match the model. The HNSW index in each graph.lbug is bound to this dimension at create time — changing it requires a graph rebuild.
Changing KG_EMBEDDING_MODEL or KG_EMBEDDING_DIM after a board has been created will not re-embed existing nodes. New nodes will be embedded with the new model, but similarity search will mix vector spaces. If you need to switch models, run okto-pulse kg backfill <board_id> to recompute everything; if the dimension changed, drop and rebuild the graph.

Knowledge graph — runtime tuning

LadybugDB engine sizing. The legacy KUZU prefix is retained in env-var names because the underlying engine is the Kùzu-derived LadybugDB (Kuzu retired 2026-05-03). All three are validated by pydantic.Field with the ranges shown.
Env varDefaultRangeFieldNotes
KG_KUZU_BUFFER_POOL_MB25616–512 MBkg_kuzu_buffer_pool_mbLadybugDB buffer pool. The library’s own default (buffer_pool_size=0) maps to ~80 % of system RAM and caused 128 GB RSS with three pooled boards in field reports — hence the conservative cap. (inventory:194, config.py:71–73)
KG_KUZU_MAX_DB_SIZE_GB11–64 GBkg_kuzu_max_db_size_gbPer-board graph size cap. The library default (1<<43 = 8 TB virtual address) is replaced with this saner default. (inventory:195)
KG_CONNECTION_POOL_SIZE81–32kg_connection_pool_sizeLadybugDB connection pool per process. (inventory:196)

Knowledge graph — consolidation queue

The KG worker pool that drains the SQLite consolidation queue and writes nodes/edges into LadybugDB. All KG_QUEUE_* settings hot-reload via APScheduler with a 5-second debounce — no restart required. (inventory:790–793, config.py:80–87)
Env varDefaultRangeFieldNotes
KG_SESSION_TTL_SECONDS3600int (s)kg_session_ttl_secondsConsolidation session TTL. Sessions older than this are reaped by the cleanup worker. (inventory:197)
KG_CLEANUP_INTERVAL_SECONDS60int (s)kg_cleanup_interval_secondsHow often the cleanup worker runs. (inventory:198)
KG_CLEANUP_ENABLEDtrueboolkg_cleanup_enabledDisable the cleanup worker entirely (rare; useful for replay tests). (inventory:199)
KG_QUEUE_MAX_CONCURRENT_WORKERS41–16kg_queue_max_concurrent_workersAsyncio task concurrency inside the single uvicorn process. Not OS workers. (inventory:200)
KG_QUEUE_MIN_INTERVAL_MS1000–1000 mskg_queue_min_interval_msFloor between queue polls. Lower = more aggressive draining; higher = lower CPU. (inventory:201)
KG_QUEUE_CLAIM_TIMEOUT_S30060–3600 skg_queue_claim_timeout_sAfter this, an in-flight queue entry is considered abandoned and reclaimed by another worker. (inventory:202)
KG_QUEUE_MAX_ATTEMPTS51–10kg_queue_max_attemptsRetries per entry before it dead-letters. Reprocess via okto_pulse_kg_dead_letter_reprocess. (inventory:203)
KG_QUEUE_ALERT_THRESHOLD5000100–100000kg_queue_alert_thresholdQueue depth that surfaces as a yellow warning in okto_pulse_kg_health. (inventory:204)
KG_QUEUE_RECOVERY_SCAN_INTERVAL_S6010–600 skg_queue_recovery_scan_interval_sPeriodicity of the dead-entry recovery scan. Values below 30 s start to compete with normal traffic. (inventory:205)
KG_MAX_QUEUE_DEPTH is deprecated as of v0.1.14 and will be removed in v0.5.0. The settings_service maps the legacy value into KG_QUEUE_ALERT_THRESHOLD and emits a DeprecationWarning at startup. Source: config.py:67–70.

Knowledge graph — relevance decay

The decay tick recomputes node relevance scores so old, unread decisions sink. Hot-reloadable through PUT /settings/runtime — APScheduler reschedules the job in place. (inventory:206–208, config.py:90–95)
Env varDefaultRangeFieldNotes
KG_DECAY_TICK_INTERVAL_MINUTES1440 (24 h)5–10080 (5 min – 7 d)kg_decay_tick_interval_minutesCron period for the decay worker. The 5-minute floor blocks accidental DoS-against-self; the 7-day ceiling blocks “forgot to schedule it”.
KG_DECAY_TICK_STALENESS_DAYS71–365 dkg_decay_tick_staleness_daysNodes whose last_recomputed_at is older than this are recomputed on the next tick.
KG_DECAY_TICK_MAX_AGE_DAYS00–365 dkg_decay_tick_max_age_days0 = no cap. When set, force-recompute “fresh” nodes older than this many days regardless of staleness.
KG_DECAY_TICK_BATCH_SIZE200intmodule constantMaximum stale KG nodes processed per decay tick batch, ordered by id so a tick does not revisit the same row. Source: events/handlers/kg_decay_tick.py:40.
KG_DAILY_TICK_DISABLEDunset1 disablesruntime guardDisables scheduled daily KG tick registration. Used by tests and controlled runtimes that trigger ticks manually. Source: core/app.py:164 and community/main.py:372.

MCP tracing

Independent of standard logging, Pulse can record every MCP tool call as JSONL. Read at runtime by core/mcp/trace_middleware.py:49,56.
Env varDefaultNotes
MCP_TRACE_ENABLEDunsetSet to 1, true, or yes to enable. Anything else (or unset) keeps tracing off.
MCP_TRACE_DIR${KG_BASE_DIR}/mcp_tracesOutput directory; one session_*.jsonl per MCP session. The Docker image overrides to /data/mcp_traces.
MCP_TRACE_ENABLED=1 okto-pulse serve --accept-terms
$ ls ~/.okto-pulse/mcp_traces/
session_2026-05-07T14-12-03_a3f9.jsonl
$ head -1 ~/.okto-pulse/mcp_traces/session_2026-05-07T14-12-03_a3f9.jsonl
{"ts":"2026-05-07T14:12:04Z","tool":"okto_pulse_get_active_board","args":{},"result_summary":"ok","ms":8}

MCP authentication

Authentication for okto-pulse MCP tools is configured via MCPSettings (config.py:110–117) with env_prefix="MCP_" — these are the only Pulse env vars that take a prefix.
Env varDefaultFieldNotes
MCP_REQUIRE_AGENT_KEYtrueMCPSettings.require_agent_keyWhen true, every MCP request must carry a valid dash_<hex> API key (query param or Authorization: Bearer). Disabling this exposes the server unauthenticated — do not unless the listener is on a private socket.
MCP_AGENT_KEYS_ENVunsetMCPSettings.agent_keys_envComma-separated list of agent keys for static validation. Normally agents are seeded into SQLite by okto-pulse init --agents; this override is for ephemeral CI environments.
For the auth flow itself — query-param vs. Bearer header vs. .mcp.json discovery — see MCP reference / Authentication.

Docker-image-only

Set inside the official ghcr.io/oktolabsai/okto-pulse image; not configurable from CoreSettings. Source: okto-pulse/Dockerfile:28–29.
Env varContainer defaultPurpose
HF_HOME/opt/hf-cacheHugging Face cache root. Pre-warmed at build time with all-MiniLM-L6-v2; do not mount over this path.
SENTENCE_TRANSFORMERS_HOME/opt/hf-cache/sentence_transformersSentence-transformers sub-cache.
Mounting a volume over /opt/hf-cache forces the container to re-download the embedding model on first run. The image is offline-capable only because the cache is pre-baked.

.env example

okto-pulse serve reads a .env file from its working directory. Useful for local development.
.env
# Server
ENVIRONMENT=development
DEBUG=true
CORS_ORIGINS=http://localhost:5173,http://localhost:3000

# Storage
DATA_DIR=/Users/you/.okto-pulse

# Knowledge graph — sizing
KG_KUZU_BUFFER_POOL_MB=384
KG_CONNECTION_POOL_SIZE=12

# Knowledge graph — queue
KG_QUEUE_MAX_CONCURRENT_WORKERS=8
KG_QUEUE_ALERT_THRESHOLD=10000

# Tracing (debug only — verbose)
# MCP_TRACE_ENABLED=1
okto-pulse serve --accept-terms
INFO  okto_pulse  Starting Okto Pulse Community...
INFO  okto_pulse.kg  KG buffer pool=384MB, connection pool=12, max workers=8
INFO  uvicorn  Uvicorn running on http://127.0.0.1:8100
Variables set in the process environment override values from .env. Production deployments should set vars in the systemd unit, the Docker --env/-e flag, or the orchestrator manifest — not check a .env into the repo.

Hot-reloadable vs. restart-required

Most settings are read once at startup. The KG_QUEUE_* and KG_DECAY_TICK_* families are an exception — they hot-reload while the process is running.
FamilyBehavior
Server (HOST, PORT, MCP_HOST, CORS_ORIGINS)Restart required. Bound at uvicorn start.
Storage (DATA_DIR, KG_BASE_DIR, DATABASE_URL, UPLOAD_DIR)Restart required. SQLite engine and LadybugDB connections are opened once.
KG embeddings (KG_EMBEDDING_*)Restart required, plus a backfill if you want existing nodes re-embedded.
KG runtime (KG_KUZU_*, KG_CONNECTION_POOL_SIZE)Restart required. LadybugDB pool is constructed at startup.
KG queue (KG_QUEUE_*)Hot-reload. APScheduler re-reads on every claim with a 5 s debounce. (inventory:790–791)
KG decay tick (KG_DECAY_TICK_*)Hot-reload via PUT /settings/runtime. APScheduler reschedules the job in place. (inventory:792–793)
MCP tracing (MCP_TRACE_ENABLED, MCP_TRACE_DIR)Restart required. Read once at MCP-process start by the trace middleware.

Server configuration

Ports, bind hosts, CORS, logging, and worker tuning in depth.

Storage paths

The on-disk layout the storage variables control, plus backup and migration.

MCP reference

The 198 MCP tools, including auth flow and the tracing knobs above.

Docker install

The pre-baked container that sets HOST, MCP_HOST, DATA_DIR, and HF_HOME for you.
Last modified on May 8, 2026