Veefore docs

Run the stack

Three processes, what each is responsible for, how to check they are healthy, and how to scale them independently.

#The three processes

Process Entry point Serves Reached by
Control plane apps/control-plane/src/server.ts /v1/* People, console, CLI, your CI
Gateway gateway/src/server.ts /gateway/v1/* Agents only
Console apps/console The browser app People

Plus a background worker, apps/control-plane/src/worker.ts, which runs the job queues: webhook fan-out and delivery, usage rollups, risk baseline recomputation, notifications, and reclaiming abandoned jobs.

The split is a security boundary, not a packaging choice

The gateway is the only process agents can reach. It has a smaller HTTP surface, it accepts no human credentials, and it can be scaled, network-isolated and hardened independently of the management API.

Do not merge them. Do not expose /v1/* on the same ingress agents reach.

#Health

bash
curl -s http://localhost:4000/health/live
curl -s http://localhost:4000/health/ready
curl -s http://localhost:4010/health/live
curl -s http://localhost:4010/health/ready
Endpoint Means Use as
/health/live The process is up and the event loop is responsive Liveness probe
/health/ready Dependencies are reachable — the database, in particular Readiness probe

/health/ready genuinely checks the database. If the database is gone, readiness goes red rather than reporting healthy and then failing every request.

This was a real bug worth knowing about

The gateway's readiness check once stayed green with no database, because the embedded driver silently booted a fresh empty database on the next query after being closed. A "healthy" gateway with an empty database is worse than a red one, because your load balancer keeps sending it traffic. The driver now treats close as terminal, and readiness reflects reality.

#Scaling

Both planes are stateless. Session state and the job queue live in the database. Run as many instances as you need behind a load balancer.

Scale them independently. They have completely different load profiles: the gateway handles agent traffic, which is high volume and latency sensitive; the control plane handles human and CI traffic, which is low volume and bursty. Typical ratio is 3:1 gateway to control plane.

Run at least one worker. Job claiming is tenant-safe and guarded against overlapping poll ticks, so several workers can run concurrently without duplicating work. With no worker, webhooks never deliver and usage never rolls up — and nothing visibly breaks, which is the annoying part.

Another real bug worth knowing about

Every job claim once hit the repository's tenant-scope guard. The error was swallowed by the poll loop, and 67 jobs wedged every queue permanently with no alert. Claiming now carries an explicit cross-tenant justification, abandoned jobs are reclaimed and dead-lettered after repeated failure, and overlapping ticks are prevented.

Monitor queue depth. A silently stalled queue is the failure mode that does not page anyone.

Redis is optional. Leave REDIS_URL empty for the in-process store, which is correct for a single-node run. Set it for multi-node, so rate limiting and nonce tracking are shared — otherwise each instance has its own view and the effective rate limit is multiplied by your instance count.

#Starting it

Development:

bash
npm run dev                # all three, from the root .env
npm run dev:control-plane
npm run dev:gateway
npm run dev:console

Production:

bash
npm run build
node dist/control-plane/server.js
node dist/gateway/server.js
node dist/control-plane/worker.js

The console builds to static assets. Serve them from any static host or CDN; it talks to the control plane from the browser, so CORS_ALLOWED_ORIGINS must include wherever you serve it from.

#Ordering during a deploy

  1. Migrations. npm run migrate. Migrations are forward-only and designed to be safe against the previous version, so this runs before any process restarts.
  2. Control plane. Rolling.
  3. Gateway. Rolling.
  4. Worker. It can restart at any point; in-flight jobs are reclaimed.
  5. Console. Static assets; publish last.

Gateway last among the request-serving processes, so the control plane is already on the new schema when the gateway starts depending on it.

#Logs

Structured JSON through pino. LOG_LEVEL controls verbosity; info is the production default.

Every line carries requestId and traceId, so a request_id from an error response finds every log line that request produced.

Redaction is applied on the way in, not on the way out. There is no code path where a secret is written to a log and later filtered.

Do not raise the log level to debug in production and leave it

debug logs request and response shapes. Redaction still applies to known secret shapes, but the volume alone will cost you, and a log retained for 90 days is a much larger surface than one retained for 7.

#Metrics

METRICS_ENABLED=true exposes Prometheus metrics at /metrics. See Observability for what to alert on.

#What each process needs

Control plane Gateway Worker
Database yes yes yes
VEEFORE_DATA_ENCRYPTION_KEY yes yes yes
VEEFORE_SIGNING_PRIVATE_KEY yes yes —
VEEFORE_SESSION_SECRET yes — —
VEEFORE_AUDIT_CHAIN_SECRET yes yes yes
Outbound egress for webhooks for tool execution for webhooks
Inbound from agents no yes no
Inbound from people yes no no

The gateway does not need VEEFORE_SESSION_SECRET, because it never issues a human session. Keep it out of the gateway's environment — a secret a process does not need is a secret it should not have.

Identity, authority, policy and enforcement for autonomous AI agents.