Local development
A fast loop with real decisions — simulated execution by default, a real downstream when you want one.
The goal locally is real decisions with harmless execution. Veefore gives you that by default: the pipeline runs for real and the tool runtime is simulated until you deliberately turn egress on.
#Start the stack
bashnpm run dev
| Control plane | http://localhost:4000 |
| Gateway | http://localhost:4010 |
| Console | http://localhost:5173 |
Full setup in Self-host Veefore.
#Seed something to look at
bashnpm run seed:demo
Creates an organization with agents, tools, policies, delegations, credentials,
approvals, incidents and audit history — enough that every console page renders
something real. The accounts are printed at the end; the owner is
owner@acme.test.
npm run seed gives you an empty organization instead.
Reseeding destroys what you created
seed:demo resets the dataset. Anything you set up by hand goes with it. If you
have a working configuration you care about, script it — see
examples/setup-real.ts for the pattern, which provisions everything through
the public API.
#Execution is simulated by default
GATEWAY_ALLOW_REAL_EGRESS=false is the default, and with it the tool runtime
produces deterministic simulated responses. The full pipeline still runs: real
authentication, real policy, real risk, real approvals, real audit. Only the
outbound call is simulated.
Every simulated response carries simulated: true. Nothing is presented as
output from a real system when it is not.
That default means you cannot accidentally refund a real customer while testing a policy. It also means you cannot catch a credential or destination problem, because nothing is actually called.
#Then make it real, safely
The repository ships a genuine downstream service for exactly this.
bashnode examples/real-tool-server.mjs
A real HTTP service on loopback :4500 with real mutable state and its own API
key:
POST /refunds |
Requires x-api-key. Appends to a ledger |
GET /ledger |
Open. Shows what actually happened |
Turn egress on:
iniGATEWAY_ALLOW_REAL_EGRESS=true
TOOL_SERVER_PORT=4500
TOOL_SERVER_API_KEY=downstream-secret-key
Then:
bashnpx tsx examples/setup-real.ts # provisions everything through the public API
npx tsx examples/run-real-agent.ts --enforce
curl -s http://localhost:4500/ledger
The ledger is the point. It shows you what actually reached the downstream, which is the only way to confirm enforcement did what the decision said it did. See Examples.
Plaintext HTTP is allowed to loopback only
The egress guards require HTTPS unless the host is a loopback address. That is
why the example service runs on 127.0.0.1 — a plaintext endpoint on any other
host is refused with DESTINATION_BLOCKED.
#Watch decisions live
bashveefore dev
Streams every decision for this agent with the policy and rule behind it, and
the next concrete step for each outcome. --all for every agent in the
environment.
bashveefore logs --follow --decision BLOCK
veefore status
#Use SIMULATE, not OBSERVE
For a development environment, SIMULATE is the better mode:
bashveefore deploy --mode SIMULATE --reason "Local development"
SIMULATE gives you real refusals with no execution, so your error handling is
exercised. OBSERVE lets everything through, which means your code never sees a
refusal locally and then sees its first one in production.
#Iterating on policy
bashveefore policies test
veefore policies diff pol_01ABC --from 3 --to 4
Simulation is the fastest loop, because it has no side effects at all:
bashcurl -X POST http://localhost:4000/v1/policies/simulate \
-H "Authorization: Bearer $VEEFORE_API_KEY" \
-H 'content-type: application/json' \
-d '{ "agentId": "agt_01JAV…", "tool": "refund_customer", "amount": 150000 }'
#Ports
4000 and 5173 are commonly taken, so expect to change at least one. Change it
in .env — everything reads from there, including the console's Vite config.
| Variable | Default |
|---|---|
CONTROL_PLANE_PORT |
4000 |
GATEWAY_PORT |
4010 |
VEEFORE_CONSOLE_PORT |
5173 |
VEEFORE_CONSOLE_PREVIEW_PORT |
4173 |
VEEFORE_DOCS_PORT |
4800 |
TOOL_SERVER_PORT |
4500 |
Changing the console port means changing CORS_ALLOWED_ORIGINS to match, or the
browser refuses every request.
#Avoid two traps
Ambient VEEFORE_* variables. A shell with VEEFORE_AGENT_ID exported from
an earlier session will quietly make a test authenticate as the wrong agent.
Shell environments persist. If something is behaving as though it is a different
agent, check env | grep Veefore.
The embedded database. PGlite is per-process. Pointing the stack at it gives the control plane, the gateway and the seed script each a private copy of the database, and the symptom is that an agent you just created does not exist. Use a real PostgreSQL for anything multi-process.