JoyZoning is local-first: all state and settings stay on your machine. There is no cloud control plane.
First-time setup (paths, ports, keys explained for operators): onboarding/installation.md · hermes-setup.md · api-keys-and-models.md.
| Layer | Location | Used for |
|---|---|---|
| appsettings.json | src/JoyZoning.ControlPlane/appsettings.json |
Committed defaults (placeholder InstallRoot) |
| appsettings.example.json | Same directory | Copy template with documented LeaseRuntime block |
| appsettings.Development.json | Same directory (gitignored) | Your machine: real InstallRoot, local overrides |
SQLite app_config |
Inside joyzoning.db |
Settings saved from the desktop UI (PUT /api/config) |
| Environment variables | Shell / .env |
CLI (JOYZONING_*), optional DB override, Hermes hints |
| onboarding.json | Application Support | First-run checklist, surface tips, last workspace |
Runtime Hermes HTTP clients reload from saved config without restarting the control plane.
| Service | Setting key | Default |
|---|---|---|
| Control plane | ControlPlane:ListenUrl |
http://127.0.0.1:9470 |
| Hermes API | Hermes:ApiBaseUrl |
http://127.0.0.1:8642 |
| Hermes dashboard | Hermes:DashboardBaseUrl |
http://127.0.0.1:9119 |
| Hermes profile | Hermes:Profile |
joyzoning |
Shell override (wins over SQLite on control-plane startup): JOYZONING_HERMES_PROFILE=default.
CLI helpers: jz config hermes, jz config hermes sync-model --from default, jz config hermes use <profile>.
cp src/JoyZoning.ControlPlane/appsettings.example.json \
src/JoyZoning.ControlPlane/appsettings.Development.jsonEdit Hermes:InstallRoot in Development only. ASP.NET Core merges appsettings.json + appsettings.Development.json when ASPNETCORE_ENVIRONMENT=Development.
{
"Hermes": {
"InstallRoot": "/path/to/diet-hermes-main-master",
"Profile": "joyzoning",
"ApiBaseUrl": "http://127.0.0.1:8642",
"DashboardBaseUrl": "http://127.0.0.1:9119",
"AutoStartGateway": true
},
"ControlPlane": {
"ListenUrl": "http://127.0.0.1:9470"
}
}Hermes:AutoStartGateway — when true, POST /api/hermes/ensure can spawn the gateway process.
Optional; defaults come from LeaseRuntimeOptions in JoyZoning.Domain:
| Key | Default | Role |
|---|---|---|
MaxGlobalActiveLeases |
16 | Cap concurrent active leases |
MaxActiveLeasesPerSession |
8 | Per operator session |
MaxCriticalLeases |
1 | Global critical slot |
ReconciliationIntervalSeconds |
60 | Background reconciliation period |
AbsoluteExpirationRevokes |
false | Past ExpiresAt → revoked vs blocked |
Duration.LowHours |
8 | Lease TTL by risk |
Duration.MediumHours |
4 | |
Duration.CriticalHours |
2 | |
Stale.LeasedMinutes |
30 | Heartbeat staleness thresholds |
Stale.RunningMinutes |
45 | |
Stale.VerifyingMinutes |
90 | |
Stale.CriticalLeasedMinutes |
10 | Shorter for critical cards |
Stale.CriticalRunningMinutes |
20 | |
Stale.CriticalVerifyingMinutes |
45 | |
WorktreeMonitorEnabled |
(removed) | Use GET /api/tasks/{id}/workspace/changed + SignalR instead |
Example override in appsettings.Development.json:
{
"LeaseRuntime": {
"MaxCriticalLeases": 1,
"Stale": { "RunningMinutes": 30 }
}
}When several cards run at once on the same project folder:
| Key | Default | Role |
|---|---|---|
DeferFullKanbanSyncWhenActiveLeasesAtLeast |
2 | Skip full two-way kanban sync while this many active leases exist on the workspace |
KanbanOutboxPollIntervalMs |
250 | Backoff after outbox drain errors |
LargeChangeSetFileThreshold |
20 | Risk flag when changed-file count is at or above this value |
Status changes are queued to a single drain worker per process (serialized PATCH per task). Executor dispatches use an isolated HermesSessionId on each ExecutionSession; the manager keeps OperatorSession.HermesSessionId.
JSDP execution uses the canonical workspace only. Legacy .joyzoning/worktrees/ and .joyzoning/live/ directories are pruned on merge/revoke.
| Data | macOS path | Override |
|---|---|---|
| SQLite database | ~/Library/Application Support/JoyZoning/joyzoning.db |
JoyZoning:DatabasePath or JOYZONING_DB_PATH |
| Onboarding state | ~/Library/Application Support/JoyZoning/onboarding.json |
— |
| Sample workspace | ~/Library/Application Support/JoyZoning/workspaces/getting-started |
Created when no project open |
| Execution workspace | Canonical session root (branch joyzoning/card-<id>) |
JSDP default; no isolated sandboxes |
| Agent context file | <worktree>/.joyzoning/context.json |
Written on dispatch / jz agent start |
| Variable | Default | Purpose |
|---|---|---|
JOYZONING_URL |
http://127.0.0.1:9470 |
Control plane base URL for jz |
JOYZONING_SESSION_ID |
— | Default session for task commands |
JOYZONING_TASK_ID |
— | Default task id when omitted |
JOYZONING_DB_PATH |
— | SQLite path (tests, dogfood) |
See .env.example:
| Variable | Purpose |
|---|---|
HERMES_INSTALL_ROOT |
diet-hermes checkout path |
HERMES_API_URL |
API base (8642) |
HERMES_DASHBOARD_URL |
Dashboard base (9119) |
HERMES_PROFILE |
Hermes profile name (joyzoning) |
HERMES_DASHBOARD_SESSION_TOKEN |
Optional manual token for kanban + TUI |
The desktop Settings UI and PUT /api/config are the preferred way to persist Hermes connection values.
JoyZoning deliberately uses one Hermes tree:
- Default:
~/Downloads/diet-hermes-main-master - Installed by
scripts/install-diet-hermes.shon first auto-setup - Manager and executor are different sessions on the same gateway, synced via kanban
There is no second “slave” Hermes install in the product model.
Saved via Settings and exposed on GET /api/config:
- Auto-import — periodic
POST /api/tasks/import-kanban - Interval — seconds between syncs
- Dashboard token — required for Hermes kanban plugin API
Two-way sync: import pulls Hermes board; local-only tasks and status changes push back. Local-wins on pull for tasks edited in JoyZoning since last sync.
When ASPNETCORE_ENVIRONMENT=Testing:
- Control plane does not bind
ListenUrl(in-procWebApplicationFactoryor dogfood subprocess sets port) TestAgentHostSetupreplaces real Hermes adapters with stubs- Tests set
JOYZONING_DB_PATHto an isolated SQLite file