Recipe: run the local dev loop
harbor dev boots a local Runtime + Protocol server on the loopback, mints an ephemeral dev token, and serves until you stop it. Pair it with harbor validate as a pre-boot check.
Steps
Build the CLI:
shmake buildWrite a config. Start from the annotated reference at
examples/dev.yaml— copy it toharbor.yamlat your project root. The two fields you almost always edit:identity.issuer/identity.audience/identity.jwks_url— point at a real OIDC provider for non-local deployments. The dev loop mints its own ephemeral ES256 token for local convenience.llm.api_key— uses anenv.NAMEreference (e.g.env.OPENROUTER_API_KEY). The bifrost driver resolves it viaos.Getenvat boot; a missing env var fails closed withErrMissingAPIKey(CLAUDE.md §13 — fail loudly).
Validate before booting:
shharbor validate ./harbor.yamlharbor validateruns the in-process config loader without booting any subsystem and emits stable, file:line-precise errors — suitable as a CI pre-flight gate. Exit0= valid,1= validation errors,2= unexpected error.Boot the dev loop:
shexport OPENROUTER_API_KEY=sk-or-... harbor devFlags:
--config <path>— config file; defaults toharbor.yaml.--port <int>— loopback port; defaults to18080(also overridable via theHARBOR_BINDenv var).--no-hot-reload— disable the fsnotify-driven hot-reload watcher (overridescli.dev_hot_reload.enabled).
harbor devserves untilSIGINT/SIGTERM, then shuts down gracefully.
The dev-only mock LLM escape hatch
For first-clone convenience and CI smoke with no real provider:
HARBOR_DEV_ALLOW_MOCK=1 harbor devThis prints a [DEV-ONLY MOCK LLM — DO NOT USE IN PRODUCTION] stderr banner on every boot. The default path with no flag set demands a real provider (CLAUDE.md §13).
Notes
harbor devboots the Runtime only — it does NOT serve the Console. The Console runs via the separateharbor consolesubcommand (D-091).Governance tiers enforce (Phase 111a, D-198): a populated
governance.identity_tiersblock inharbor.yamlgates LLM calls at boot — cost ceilings, rate limits, and per-call MaxTokens each reject with a typed error and agovernance.*event on the stream. Empty tiers stay fully latent (D-044). Headless embedders running multiple stacks composegovernance.Wrapinstead — seedocs/recipes/embed-harbor-headless.md.Hot reload watches the config file's directory and
.harbor/agentsby default; an edit toharbor.yamltriggers a drained reload.Config and YAML changes hot-reload;
.gochanges do not. The watcher rebuilds the running stack in process — it re-readsharbor.yaml, re-wires the LLM / tools / memory, and re-binds the Protocol server. That picks up every config / YAML / scaffold edit because nothing needed recompiling. A change to an in-process tool's.gosource is different: an in-process rebuild does not invoke the Go compiler, so the edited code is never loaded. Rather than reboot and falsely report a successful reload,harbor devlogs a warning and tells you to rebuild by hand:textWARN hot-reload: Go source change detected — harbor dev does not recompile Go; run `make build` and restart `harbor dev` to pick it up (config / YAML changes hot-reload in-process; .go changes do not)The fix is the same two-step cycle you'd run without hot reload:
shmake build && harbor dev(An opt-in
policy: rebuild-binarythat runsgo buildand re-execs on a.gochange is not implemented —harbor devwarns and guides only.)To inspect a running dev server, use
harbor inspect-eventsandharbor inspect-runsagainst the same loopback.