Skip to content

Production

What a production Venkai deployment has, and — just as usefully — what it does not.

Readiness checklist

[ ] docker build succeeds (prove the artefact, don't assume it)
[ ] VENKAI_JWT_SECRET set, ≥32 bytes, stored in a secret manager
[ ] POSTGRES_PASSWORD set, not reused anywhere else
[ ] VENKAI_ENV=production          → rate limiting + Secure cookies
[ ] VENKAI_ALLOWED_ORIGINS set to your dashboard origin only
[ ] VENKAI_EMBEDDING_PROVIDER chosen deliberately
[ ] .env confirmed gitignored (git check-ignore -v .env)
[ ] Alembic migration run explicitly
[ ] Port 8100 NOT published to the host
[ ] TLS verified from outside the host
[ ] /api/health returns {"status":"ok","database":"ok"} externally
[ ] Full write→read round trip verified with a real API key
[ ] Database backup scheduled AND a restore tested
[ ] Alerting on /api/health

Configuration

VENKAI_ENV=production
VENKAI_JWT_SECRET=<from your secret manager>
POSTGRES_PASSWORD=<from your secret manager>
VENKAI_ALLOWED_ORIGINS=https://app.your-domain
VENKAI_EMBEDDING_PROVIDER=semantic

VENKAI_ENV=production is load-bearing: it is what turns on rate limiting and the Secure flag on session cookies. A deployment left at the development default is publicly reachable with no rate limiting at all.

Secrets

Secret Rotation
VENKAI_JWT_SECRET Logs every user out. Treat as permanent once a pilot starts.
POSTGRES_PASSWORD Rotate with a coordinated database + app restart
API keys POST /api/auth/api-keys/{id}/rotate — no grace period, deploy the new key first

Keep them in a secret manager, injected at runtime. .env on the host is acceptable for a small deployment provided file permissions are tight and it is never committed.

Persistence

Store PostgreSQL. SQLite is a development convenience, not a production store.
Retention None. Memories persist until edited or the org is deleted.
Growth Linear in memories written. ~7.5 KB per memory of JSON embedding at 384-d.
Backups Yours to schedule. See Self-hosting.

Watch project size: retrieval is O(memories in project) and VENKAI_RETRIEVAL_MAX_CANDIDATES (10 000) truncates oldest first — losing exactly your foundational constraints.

curl -s https://your-domain/api/projects -H "Authorization: Bearer $KEY" \
  | python -c "import json,sys; [print(p['contexts'], p['project_id']) for p in json.load(sys.stdin)['projects']]"

Alert at ~7000.

Health and monitoring

GET /api/health is a readiness probe — it opens a database connection.

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8100/api/health"]
  interval: 30s
  timeout: 5s
  retries: 3
Signal Source
Availability /api/health from outside the host
Request volume by kind GET /api/usage — middleware-counted, real traffic
Store growth GET /api/projectscontexts
Candidate truncation Application logs — the only place it appears
Retrieval latency Client-side timing; the API does not expose it

There is no metrics endpoint: no Prometheus scrape target, no /metrics. Instrument at your reverse proxy or in your client.

Logging

Structured events are emitted for writes and retrievals with content hashed, never logged — digests of project and agent ids, type, character counts, estimated tokens. Volume is measurable without being readable.

Log retention, shipping and rotation are yours. PYTHONUNBUFFERED=1 in the image means logs reach your collector promptly.

Security posture

What is in place:

  • Organization scoping enforced twice — in the query, then per candidate.
  • API keys stored as hashes; the raw value is returned once and never again.
  • 404 rather than 403 for other tenants' resources, so responses cannot confirm that an id exists.
  • Rate limiting per IP in production.
  • CORS restricted to VENKAI_ALLOWED_ORIGINS.
  • Port 8100 unpublished, so X-Forwarded-For cannot be spoofed past Caddy.
  • Embeddings computed locally; content never leaves your infrastructure.

What is not:

Absent Implication
Per-key scopes or permissions Any key can read and write everything in its org
vk_test_ isolation A test key reads and writes real data
Audit log of reads Writes leave a trail; reads are counted, not logged
Encryption at rest Whatever your database provides
IP allow-listing Do it at the proxy
SSO / SCIM Not implemented

Scaling

The API is stateless — run several behind a load balancer. In-process rate limit counters are per instance, so N instances means roughly N × the limit; enforce at the proxy if the number must be exact.

The real ceiling is memories per project, not requests. When retrieval latency becomes a problem, the answers in order are: split projects, raise the candidate cap knowingly, then migrate to pgvector with an ANN index (Planned).

Not production-ready

Do not build a production dependency on these:

Feature Status
/api/security/analyze, venkai_security_gate Experimental — unvalidated scoring; do not use as a blocking CI gate
/api/handoffs Experimental — a record, not a mechanism
venkai_client_accept / _deliver Experimental
Published benchmarks PlannedEvaluation