All configuration is via environment variables. See .env.example for a minimal starting point.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
— | PostgreSQL connection string for queries and writes |
AKASHI_ADMIN_API_KEY |
— | Bootstrap API key for the admin agent. Required when the agents table is empty |
| Variable | Default | Description |
|---|---|---|
AKASHI_PORT |
8080 |
HTTP listen port |
AKASHI_READ_TIMEOUT |
30s |
HTTP read timeout |
AKASHI_WRITE_TIMEOUT |
30s |
HTTP write timeout |
AKASHI_MAX_REQUEST_BODY_BYTES |
1048576 |
Max request body size (1 MB) |
AKASHI_LOG_LEVEL |
info |
Log level: debug, info, warn, error |
AKASHI_CORS_ALLOWED_ORIGINS |
(empty) | Comma-separated allowed CORS origins. Empty = deny cross-origin browser requests unless same-origin |
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
— | PostgreSQL connection string for queries and writes. In production, point this at PgBouncer; in local dev, point directly at Postgres (port 5432) |
NOTIFY_URL |
same as DATABASE_URL |
Direct Postgres connection for LISTEN/NOTIFY (SSE). Must bypass PgBouncer — transaction-mode poolers do not support LISTEN. Set NOTIFY_URL= to disable SSE push entirely |
AKASHI_SKIP_EMBEDDED_MIGRATIONS |
false |
Skip startup embedded migrations (use when an external system like Atlas owns migration execution) |
See ADR-007 for why two connections are needed.
| Variable | Default | Description |
|---|---|---|
AKASHI_ADMIN_API_KEY |
(empty) | Bootstrap admin API key. If no agents exist and this is empty, startup fails to prevent admin lockout |
AKASHI_JWT_PRIVATE_KEY |
(empty) | Path to Ed25519 private key PEM file. Empty = ephemeral key generated on every startup — all tokens are invalidated on each restart. Use persistent keys for any real use. |
AKASHI_JWT_PUBLIC_KEY |
(empty) | Path to Ed25519 public key PEM file (must be set alongside the private key) |
AKASHI_JWT_EXPIRATION |
24h |
JWT token lifetime |
AKASHI_SIGNUP_ENABLED |
false |
Enable unauthenticated POST /auth/signup for self-serve org creation. Keep false for self-hosted; set true for cloud deployments |
Both key files must have 0600 permissions. The server rejects looser modes at startup.
Generating persistent keys (run once from the repo root):
go run scripts/genkey/main.go
# Writes: data/jwt_private.pem, data/jwt_public.pemThe docker-compose.yml mounts ./data as /data in the container and sets the key paths to /data/jwt_private.pem and /data/jwt_public.pem by default, so the keys survive container rebuilds automatically.
See ADR-005 for the full auth architecture.
For Authorization: ApiKey <agent_id>:<api_key>, send X-Akashi-Org-ID when the same agent_id exists in multiple organizations. Ambiguous API key auth requests are rejected.
| Variable | Default | Description |
|---|---|---|
AKASHI_EMBEDDING_PROVIDER |
auto |
Provider selection: auto, ollama, openai, noop |
AKASHI_EMBEDDING_DIMENSIONS |
1024 |
Vector dimensionality (must match the chosen model) |
OLLAMA_URL |
http://localhost:11434 |
Ollama server address |
OLLAMA_MODEL |
mxbai-embed-large |
Ollama embedding model |
OPENAI_API_KEY |
(empty) | OpenAI API key. Required when provider is openai |
AKASHI_EMBEDDING_MODEL |
text-embedding-3-small |
OpenAI embedding model |
In auto mode: Ollama is tried first (health check with 2s timeout), then OpenAI if OPENAI_API_KEY is set, then noop (zero vectors, semantic search disabled). See ADR-006.
| Variable | Default | Description |
|---|---|---|
QDRANT_URL |
(empty) | Qdrant URL. :6334 (gRPC) is preferred; :6333 (REST) is accepted and auto-mapped to :6334. Empty = text search fallback |
QDRANT_API_KEY |
(empty) | Qdrant API key |
QDRANT_COLLECTION |
akashi_decisions |
Qdrant collection name |
AKASHI_OUTBOX_POLL_INTERVAL |
1s |
How often the outbox worker checks for pending syncs |
AKASHI_OUTBOX_BATCH_SIZE |
100 |
Max decisions synced to Qdrant per poll cycle |
Qdrant is optional. When not configured, search falls back to PostgreSQL full-text search (tsvector/tsquery) with ILIKE as secondary fallback. See ADR-002.
| Variable | Default | Description |
|---|---|---|
AKASHI_RATE_LIMIT_ENABLED |
true |
Enable rate limiting middleware |
AKASHI_RATE_LIMIT_RPS |
100 |
Sustained requests per second per key |
AKASHI_RATE_LIMIT_BURST |
200 |
Token bucket capacity (max burst size) per key |
AKASHI_TRUST_PROXY |
false |
When true, use X-Forwarded-For for IP-based rate limits (e.g. behind load balancer) |
Keys are constructed as org:<uuid>:agent:<id> for authenticated requests. For unauthenticated paths (e.g. /auth/token), the key is ip:<client_ip>. Enable AKASHI_TRUST_PROXY only when behind a trusted reverse proxy; otherwise X-Forwarded-For can be spoofed.
The OSS distribution uses an in-memory token bucket. Enterprise deployments can substitute a Redis-backed implementation via the ratelimit.Limiter interface.
| Variable | Default | Description |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
(empty) | OTLP HTTP endpoint. Empty = OTEL disabled |
OTEL_EXPORTER_OTLP_INSECURE |
false |
Use HTTP instead of HTTPS for OTLP |
OTEL_SERVICE_NAME |
akashi |
Service name in OTEL spans and metrics |
Set AKASHI_CONFLICT_PROFILE to apply a coherent set of threshold defaults. Individual env var overrides always take precedence over the profile.
| Profile | Significance | Early Exit | Claim Topic Sim | Claim Div | Decision Topic Sim | Cross-Encoder | Decay Lambda | Use case |
|---|---|---|---|---|---|---|---|---|
balanced |
0.30 | 0.25 | 0.60 | 0.15 | 0.70 | 0.50 | 0.01 | General-purpose (default) |
high_precision |
0.40 | 0.35 | 0.65 | 0.20 | 0.75 | 0.60 | 0.01 | Fewer false positives, may miss marginal real conflicts |
high_recall |
0.20 | 0.15 | 0.55 | 0.10 | 0.65 | 0.35 | 0.005 | Catches more real conflicts, accepts more noise |
| Variable | Default | Description |
|---|---|---|
AKASHI_CONFLICT_PROFILE |
balanced |
Named profile: balanced, high_precision, or high_recall. Sets coherent defaults for all thresholds below. Individual overrides take precedence |
AKASHI_CONFLICT_CANDIDATE_LIMIT |
20 |
Max candidates retrieved from Qdrant per decision. Lower values reduce LLM cost; higher values improve recall for embedding-only scoring |
AKASHI_CONFLICT_SIGNIFICANCE_THRESHOLD |
0.30 |
Min significance (topic_sim × outcome_div) to store a conflict |
AKASHI_CONFLICT_EARLY_EXIT_FLOOR |
0.25 |
Min pre-LLM significance for early exit pruning. Candidates are sorted by significance descending; once significance drops below this floor (and the candidate doesn't qualify for the bi-encoder bypass), remaining candidates are skipped. Set to 0 to disable early exit |
AKASHI_CONFLICT_CLAIM_TOPIC_SIM_FLOOR |
0.60 |
Min cosine similarity for two claims to be considered "about the same thing." Below this, claims are too unrelated to constitute a conflict |
AKASHI_CONFLICT_CLAIM_DIV_FLOOR |
0.15 |
Min outcome divergence between two claims to count as a genuine disagreement. Below this, claims effectively agree |
AKASHI_CONFLICT_DECISION_TOPIC_SIM_FLOOR |
0.70 |
Min decision-level topic similarity to activate claim-level scoring. Below this, decisions are about different enough topics that claim analysis adds noise |
AKASHI_CONFLICT_REFRESH_INTERVAL |
30s |
Interval for broker to poll new conflicts (SSE push). Conflicts are populated event-driven on trace. |
AKASHI_CONFLICT_LLM_MODEL |
(empty) | LLM model for conflict validation. Set to an Ollama model name (e.g. qwen3.5:9b) to use local validation, or leave empty to auto-detect (OpenAI if OPENAI_API_KEY is set, otherwise noop). |
AKASHI_CONFLICT_LLM_THREADS |
floor(NumCPU/3), min 1 |
CPU threads Ollama may use per inference call. Caps Ollama thread usage so conflict validation does not starve the main request-handling goroutines. Set to 0 to let Ollama decide (uses all available cores). |
AKASHI_CONFLICT_BACKFILL_WORKERS |
4 |
Number of parallel workers for conflict backfill scoring on startup. Each worker makes one LLM validation call at a time. |
AKASHI_CONFLICT_DECAY_LAMBDA |
0.01 |
Temporal decay rate for conflict significance. Higher values penalize older decision pairs more aggressively. Set to 0 to disable temporal decay. |
AKASHI_CONFLICT_CROSS_ENCODER_URL |
(empty) | URL of an external cross-encoder reranking service. When set, candidate pairs are scored for contradiction likelihood before LLM validation; pairs below AKASHI_CONFLICT_CROSS_ENCODER_THRESHOLD are filtered out, reducing LLM calls by 50-80%. The service must expose POST /score accepting {"text_a": "...", "text_b": "..."} and returning {"score": 0.0-1.0}. Empty = disabled |
AKASHI_CONFLICT_CROSS_ENCODER_THRESHOLD |
0.50 |
Minimum cross-encoder contradiction score (0-1) for a candidate pair to proceed to LLM validation. Lower values pass more pairs (higher recall, more LLM cost). Higher values filter more aggressively (lower recall, fewer LLM calls). Only effective when AKASHI_CONFLICT_CROSS_ENCODER_URL is set |
AKASHI_CLAIM_EXTRACTION_LLM |
false |
Use the conflict LLM model for structured claim extraction. When enabled, claims are extracted with categories (finding, recommendation, assessment, status) and only findings and assessments participate in conflict scoring. Requires AKASHI_CONFLICT_LLM_MODEL or OPENAI_API_KEY to be set; falls back to regex extraction if LLM is unavailable. |
AKASHI_FORCE_CONFLICT_RESCORE |
false |
When true (and an LLM validator is configured), clear all existing conflicts and re-score from scratch at startup. Use after improving the LLM prompt or claim extraction logic. One-shot flag — disable after the rescore completes. |
The WAL is enabled by default to ensure crash durability for buffered events. Without it, a process crash between flush intervals loses all in-memory events.
| Variable | Default | Description |
|---|---|---|
AKASHI_WAL_DIR |
./data/wal |
Directory for WAL segment files. Created automatically on startup if it doesn't exist. Override to place WAL on a faster disk (e.g. /var/lib/akashi/wal) |
AKASHI_WAL_DISABLE |
false |
Set to true to explicitly disable WAL (dev/testing only). Events will be buffered in memory only and lost on crash |
AKASHI_WAL_SYNC_MODE |
batch |
Sync mode: full (fsync per write, safest), batch (periodic fsync, default), none (dev only, data loss on crash) |
AKASHI_WAL_SYNC_INTERVAL |
10ms |
How often to fsync in batch mode. Lower = safer, higher = faster throughput |
AKASHI_WAL_SEGMENT_SIZE |
67108864 |
Max segment file size in bytes before rotation (default 64 MB) |
AKASHI_WAL_SEGMENT_RECORDS |
100000 |
Max records per segment before rotation |
When enabled, every event is written to the WAL before being buffered in memory. On startup, un-flushed events are recovered from the WAL and replayed to Postgres via an idempotent insert path. After a successful COPY flush, the WAL checkpoint advances and old segment files are reclaimed. See the architecture overview in internal/service/trace/wal.go.
Existing deployments that set AKASHI_WAL_DIR explicitly will continue to work unchanged. Deployments that relied on WAL being disabled by default should set AKASHI_WAL_DISABLE=true if they want to preserve the old behavior.
| Variable | Default | Description |
|---|---|---|
AKASHI_EVENT_BUFFER_SIZE |
1000 |
In-memory event buffer capacity before COPY flush |
AKASHI_EVENT_FLUSH_TIMEOUT |
100ms |
Max time between buffer flushes |
AKASHI_INTEGRITY_PROOF_INTERVAL |
5m |
How often Merkle tree proofs are built for new decisions |
AKASHI_ENABLE_DESTRUCTIVE_DELETE |
false |
Enables irreversible DELETE /v1/agents/{agent_id}. Keep false in production unless explicitly needed for GDPR workflows |
AKASHI_SHUTDOWN_HTTP_TIMEOUT |
10s |
HTTP shutdown grace timeout (0 = wait indefinitely) |
AKASHI_SHUTDOWN_BUFFER_DRAIN_TIMEOUT |
0 |
Maximum time to flush in-memory events to Postgres during shutdown. 0 = wait indefinitely (default, durability-first). Non-zero values bound the drain but risk losing unflushed events — process exits non-zero if events remain. |
AKASHI_SHUTDOWN_OUTBOX_DRAIN_TIMEOUT |
0 |
Outbox drain timeout (0 = wait indefinitely) |
AKASHI_PERCENTILE_REFRESH_INTERVAL |
1h |
How often to refresh per-org signal percentile caches used for distribution-aware ReScore normalization. Set to 0 to disable |
AKASHI_AUTO_RESOLVE_INTERVAL |
1h |
How often the background auto-resolution worker runs to resolve eligible conflicts per org policy. Set to 0 to disable |
For retry-safe write APIs (POST /v1/trace, POST /v1/runs, POST /v1/runs/{run_id}/events), clients can send:
Idempotency-Key: <unique-key>
Behavior:
- Same key + same payload => server replays the original success response (no duplicate write).
- Same key + different payload =>
409 CONFLICT. - Same key while the first request is still processing =>
409 CONFLICT(retry later).
Scope and matching rules:
- Keys are scoped by
(org_id, agent_id, endpoint, idempotency_key). - For run events,
endpointincludes the concrete run ID (for examplePOST:/v1/runs/<run_id>/events). - Payload matching uses a server-side SHA-256 hash of the canonical JSON payload:
POST /v1/trace: request body plus header-derived context that changes write semantics.POST /v1/runs: request body.POST /v1/runs/{run_id}/events: request body only.
- Replayed responses preserve the original HTTP status code and response body.
Client guidance:
- Use a UUIDv4 (or similarly random) key per logical write attempt.
- Retry transient network failures with the same key and same payload.
- On
409with "already in progress", back off and retry. - Never reuse a key for a different payload.
Operational idempotency settings:
| Variable | Default | Description |
|---|---|---|
AKASHI_IDEMPOTENCY_CLEANUP_INTERVAL |
1h |
Background cleanup cadence for idempotency records |
AKASHI_IDEMPOTENCY_COMPLETED_TTL |
168h (7d) |
Retention for completed idempotency records |
AKASHI_IDEMPOTENCY_ABANDONED_TTL |
24h |
Retention for abandoned in-progress idempotency records |
| Variable | Default | Description |
|---|---|---|
AKASHI_HOOKS_ENABLED |
true |
Enable /hooks/* IDE integration endpoints for Claude Code and Cursor. When disabled, the routes are not registered |
AKASHI_HOOKS_API_KEY |
(empty) | Optional API key for non-localhost hook access. When set, remote clients can authenticate with X-Akashi-Hook-Key header. Empty = localhost-only (recommended for local dev) |
AKASHI_AUTO_TRACE |
true |
Automatically trace git commits detected in PostToolUse hooks as decisions with confidence: 0.7. Set to false to disable auto-tracing |
Hook endpoints (/hooks/session-start, /hooks/pre-tool-use, /hooks/post-tool-use) are unauthenticated but restricted to localhost by default. They enable IDE agents to receive context injection, edit gating, and automatic decision tracing without shell-script marker files.
Akashi supports per-org data retention policies that automatically delete decisions older than a configured threshold. Policies are set via PUT /v1/retention (admin-only). Legal holds (POST /v1/retention/hold) exempt matching decisions from both automated and GDPR deletion. All deletion operations are recorded in the deletion_log table.
| Variable | Default | Description |
|---|---|---|
AKASHI_RETENTION_INTERVAL |
24h |
How often the background retention worker runs. Set to 0 to disable. |
When claim embedding generation fails during tracing (network issues, provider downtime), Akashi records the failure and retries with exponential backoff (5min, 20min, capped at 3 attempts). Successful retries automatically trigger conflict scoring.
| Variable | Default | Description |
|---|---|---|
AKASHI_CLAIM_RETRY_INTERVAL |
2m |
How often the background claim embedding retry loop runs. Set to 0 to disable. |