# zc2 Usage Guide
This guide covers the complete usage of `zc` (built from `zc2`), the Zakuro AI command-line tool. `zc` is a P2P compute broker that routes compute requests to workers across a distributed network, with credit-based billing via PostgreSQL stored procedures, P2P broker-to-broker communication, write-ahead logging, automatic worker discovery, and real-time monitoring.
---
## Table of Contents
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Broker](#broker)
- [Starting the Broker](#starting-the-broker)
- [Foreground Mode](#foreground-mode)
- [TUI Dashboard](#tui-dashboard)
- [Daemon Mode](#daemon-mode)
- [Worker Discovery](#worker-discovery)
- [Tailscale Mode](#tailscale-mode)
- [Local Mode](#local-mode)
- [Manual Registration](#manual-registration)
- [API Reference](#api-reference)
- [Health & Status](#health--status)
- [Worker Management](#worker-management)
- [Compute Execution](#compute-execution)
- [Credit Management](#credit-management)
- [Ledger Administration](#ledger-administration)
- [P2P Broker Endpoints](#p2p-broker-endpoints)
- [Routing Strategies](#routing-strategies)
- [Benchmarking](#benchmarking)
- [Basic Benchmark](#basic-benchmark)
- [Strategy Comparison](#strategy-comparison)
- [Benchmark Options](#benchmark-options)
- [Remote Monitoring](#remote-monitoring)
- [System Diagnostics](#system-diagnostics)
- [Credit System](#credit-system)
- [PostgreSQL Ledger](#postgresql-ledger)
- [Reserve-Commit Model](#reserve-commit-model)
- [API Keys](#api-keys)
- [P2P Broker-to-Broker Communication](#p2p-broker-to-broker-communication)
- [Write-Ahead Log (WAL)](#write-ahead-log-wal)
- [Cluster Commands](#cluster-commands)
- [Environment Variables](#environment-variables)
- [Configuration Reference](#configuration-reference)
---
## Prerequisites
- **Rust** toolchain (1.75+) for building from source
- **Docker** and **Docker Compose** (for containerized deployments)
- **PostgreSQL** (optional, for centralized credit ledger with restricted broker role)
- **Tailscale** (optional, for P2P network discovery)
- `ZAKURO_API_KEY` environment variable must be set
## Installation
### From Source
```bash
git clone https://github.com/zakuro-ai/zc.git
cd zc
task build:bin
```
This compiles the release binary and installs it to `/usr/local/bin/zc`.
### With Docker
```bash
task build
```
This builds the binary, then builds and starts the Docker services via `docker compose`.
### Verify Installation
```bash
zc --version
```
---
## Quick Start
1. Set your authentication token:
```bash
export ZAKURO_API_KEY="your-token-here"
```
2. Start the broker with the interactive TUI:
```bash
zc -t broker
```
3. The broker will automatically discover workers on your network and begin accepting compute requests on port `9000`.
---
## Broker
The broker is the core component of `zc`. It accepts compute requests from clients, selects an optimal worker, forwards the request, and handles billing.
### Starting the Broker
```bash
# Default: foreground mode on 0.0.0.0:9000
zc broker
# Custom port
zc broker 8080
# Custom host and port
zc broker 0.0.0.0 9000
```
### Foreground Mode
```bash
zc broker
```
Foreground mode displays a startup banner listing all API endpoints and then prints a live transaction log to stdout. Each request is shown with its status, user, cost, and latency. Press `Ctrl+C` to stop.
Example output:
```
╔═══════════════════════════════════════════╗
║ Zakuro Compute Broker ║
╚═══════════════════════════════════════════╝
Listening: http://0.0.0.0:9000
API Endpoints:
─────────────────────────────────────────────
GET /health Health check
GET /workers List workers
POST /workers Register worker
POST /workers/heartbeat Worker heartbeat
DEL /workers/:id Unregister worker
POST /execute Execute request
POST /price Estimate price
GET /credits/:user Get balance
POST /credits/:user/add Add credits
─────────────────────────────────────────────
Press Ctrl+C to stop. Use --tui for interactive TUI.
```
### TUI Dashboard
```bash
zc -t broker
zc --tui broker
zc -t broker 8080 # TUI on custom port
```
The TUI dashboard provides a real-time interactive view, similar to `htop` for compute. It has four panels:
| **Header** | Operating mode (P2P/LOCAL), ledger status (PostgreSQL/Local), uptime, listen address |
| **Transactions** | Live scrollable log showing user, action, cost, latency, and status icons |
| **Workers** | Connected workers with name, status, resource availability, and latency |
| **Metrics** | Total requests, requests/second sparkline, latency percentiles (p50/p90/p99), total credits spent |
**Keyboard shortcuts:**
| `Tab` | Switch between panels |
| `j` / `k` or `Up` / `Down` | Scroll transactions |
| `g` | Jump to top |
| `G` | Jump to bottom |
| `?` | Toggle help overlay |
| `q` or `Esc` | Quit |
### Daemon Mode
```bash
zc -d broker
zc --daemon broker
zc -d broker 8080 # Daemon on custom port
```
Daemon mode runs the broker in the background with minimal output. Useful for production deployments or running the broker as a service.
---
## Worker Discovery
The broker automatically discovers compute workers based on the available network interfaces.
### Tailscale Mode
**Trigger:** A `tailscale0`, `wg0`, or `ts*` network interface is detected with an IP in the `100.x.x.x` or `10.x.x.x` range.
**Behavior:**
- Scans the Tailscale subnet (e.g., `10.13.13.2` through `10.13.13.254`) on port `3960` (configurable via `ZAKURO_WORKER_PORT`)
- Also checks extra ports: `3961`, `3962`
- Probes each address for `/health` and `/info` endpoints
- Only registers services that return a valid `worker_type` in their `/info` response
- Refreshes heartbeats for existing workers every 15 seconds (configurable via `ZAKURO_SCAN_INTERVAL`)
- **Credits are enforced** in this mode — all executions are billed
### Local Mode
**Trigger:** No Tailscale interface is detected.
**Behavior:**
- Scans `127.0.0.1` on ports `3960`, `3961`, `3962` (or `ZAKURO_SCAN_RANGE` if set)
- Same probing logic as Tailscale mode
- **Execution is free** — no credits are charged
- Useful for development and testing
### Manual Registration
Workers can always register themselves via the REST API regardless of discovery mode. When `ZAKURO_WORKER_KEY` is configured, include the key in the `X-Worker-Key` header:
```bash
curl -X POST http://localhost:9000/workers \
-H "Content-Type: application/json" \
-H "X-Worker-Key: $ZAKURO_WORKER_KEY" \
-d '{
"name": "my-worker",
"uri": "http://10.13.13.5:3960",
"worker_type": "zakuro",
"resources": {
"cpus_total": 8.0,
"cpus_available": 8.0,
"memory_total": 17179869184,
"memory_available": 17179869184,
"gpus_total": 1,
"gpus_available": 1
},
"pricing": {
"cpu_price": 0.001,
"memory_price": 0.0001,
"gpu_price": 0.01,
"min_charge": 0.001
},
"tags": ["gpu", "cuda"]
}'
```
Workers must send periodic heartbeats to remain healthy:
```bash
curl -X POST http://localhost:9000/workers/heartbeat \
-H "Content-Type: application/json" \
-H "X-Worker-Key: $ZAKURO_WORKER_KEY" \
-d '{"worker_id": "uuid-of-worker"}'
```
Workers that miss heartbeats for longer than `worker_timeout` (default: 30 seconds) are marked unhealthy and eventually removed.
---
## API Reference
The broker exposes an HTTP API on its configured host and port (default: `0.0.0.0:9000`).
### Health & Status
#### `GET /health`
Returns `200 OK` if the broker is running.
#### `GET /stats`
Returns broker statistics in JSON format. Used by the remote TUI to render the dashboard.
**Authentication:** Requires `Authorization: Bearer <key>`. Non-admin callers only see their own transactions; admin callers see all.
**Response fields:** transactions, workers, metrics (request count, RPS, latency percentiles, credits spent).
### Worker Management
#### `GET /workers`
List all registered workers and their current status.
**Response:**
```json
{
"total": 2,
"healthy": 2,
"workers": [
{
"id": "uuid",
"name": "worker-1",
"uri": "http://10.13.13.5:3960",
"status": "Healthy",
"resources": { "cpus_total": 8, "cpus_available": 6, ... },
"pricing": { "cpu_price": 0.001, ... },
"avg_latency_ms": 12.5
}
]
}
```
#### `POST /workers`
Register a new worker. See [Manual Registration](#manual-registration) for the request body schema.
**Authentication:** Requires `X-Worker-Key` header matching `ZAKURO_WORKER_KEY` env var (when configured). Without a configured key, registration is open (local dev).
#### `POST /workers/heartbeat`
Refresh a worker's heartbeat to keep it marked as healthy.
**Authentication:** Same as `POST /workers` — requires `X-Worker-Key` when configured.
#### `DELETE /workers/:id`
Unregister a worker by its UUID.
**Authentication:** Same as `POST /workers` — requires `X-Worker-Key` when configured.
### Compute Execution
#### `POST /execute`
The primary endpoint. Sends a compute request that the broker routes to the best available worker.
**Request headers:**
| `Authorization` | Yes (remote mode) | `Bearer <api_key>` — resolves user ID for billing |
| `X-Zakuro-User` | No | Fallback user ID in local mode only (defaults to `anonymous`) |
| `X-Zakuro-Requirements` | No | JSON resource requirements (see below) |
| `Content-Type` | No | `application/octet-stream` for binary payloads |
**Requirements JSON:**
```json
{
"cpus": 1.0,
"memory_bytes": 1073741824,
"gpus": 0,
"estimated_duration_secs": 1.0,
"strategy": "best_price",
"worker_type": "zakuro",
"tags": ["gpu"]
}
```
All fields are optional and have sensible defaults (1 CPU, 1 GiB memory, 1 second duration, `best_price` strategy).
**Response headers:**
| `X-Zakuro-Request-Id` | Unique request ID |
| `X-Zakuro-Cost` | Actual cost in credits |
| `X-Zakuro-Credits-Remaining` | User's balance after the transaction |
| `X-Zakuro-Duration-Ms` | Total request duration in milliseconds |
#### `POST /price`
Estimate the cost of a request without executing it.
**Request body:** Same `requirements` JSON as `/execute`.
**Response:**
```json
{
"min_cost": 0.001,
"max_cost": 0.005,
"workers_available": 3
}
```
### Credit Management
#### `GET /credits/:user`
Get the credit balance for a user.
**Authentication:** Requires `Authorization: Bearer <key>`. Non-admin callers can only view their own balance (403 otherwise).
```bash
curl http://localhost:9000/credits/my-user \
-H "Authorization: Bearer $API_KEY"
```
#### `POST /credits/:user/add`
Add credits to a user's account. Requires an API key.
```bash
curl -X POST http://localhost:9000/credits/my-user/add \
-H "Content-Type: application/json" \
-H "X-Api-Key: your-api-key" \
-d '{"amount": 100.0, "description": "Initial deposit"}'
```
### Ledger Administration
#### `GET /ledger/status`
Check the PostgreSQL ledger connection status. The database URL password is redacted in the response.
**Authentication:** Requires `Authorization: Bearer <master_key>` (admin only).
```bash
curl http://localhost:9000/ledger/status \
-H "Authorization: Bearer $ZAKURO_MASTER_KEY"
```
### P2P Broker Endpoints
These endpoints are **only available when `ZAKURO_P2P=true`** and are used for broker-to-broker communication.
#### `GET /peer/health`
Check if the peer broker is healthy and reachable.
**Authentication:** Requires `X-Peer-Key` header matching `ZAKURO_PEER_KEY`.
```bash
curl http://peer-broker:9000/peer/health \
-H "X-Peer-Key: $ZAKURO_PEER_KEY"
```
#### `POST /peer/reserve`
Reserve credits for a user owned by this broker (called by non-authoritative brokers).
**Authentication:** Requires `X-Peer-Key` header.
**Request body:**
```json
{
"user_id": "9000000001",
"amount": 0.005,
"reservation_id": "res-uuid-123"
}
```
**Response:**
```json
{
"success": true,
"balance_remaining": 99.995
}
```
#### `POST /peer/commit`
Commit a transaction after worker execution (finalizes the reservation).
**Authentication:** Requires `X-Peer-Key` header.
**Request body:**
```json
{
"reservation_id": "res-uuid-123",
"actual_cost": 0.003,
"worker_id": "worker-uuid",
"duration_ms": 250.0
}
```
#### `POST /peer/cancel`
Cancel a reservation and refund the full amount.
**Authentication:** Requires `X-Peer-Key` header.
**Request body:**
```json
{
"reservation_id": "res-uuid-123"
}
```
#### `GET /peer/balance`
Get the balance for a user owned by this broker.
**Authentication:** Requires `X-Peer-Key` header.
**Query parameters:** `user_id=9000000001`
```bash
curl "http://peer-broker:9000/peer/balance?user_id=9000000001" \
-H "X-Peer-Key: $ZAKURO_PEER_KEY"
```
**Response:**
```json
{
"user_id": "9000000001",
"balance": 99.995
}
```
#### `POST /peer/earn`
Credit earnings to the broker owner's account (used when a remote broker pays for execution on a local worker).
**Authentication:** Requires `X-Peer-Key` header.
**Request body:**
```json
{
"request_id": "req-uuid-123",
"amount": 0.003
}
```
#### `GET /peer/workers`
List workers that are local to this broker (at `127.0.0.1` or `localhost`). Used by peer brokers to discover available workers across the mesh.
**Authentication:** Requires `X-Peer-Key` header.
---
## Routing Strategies
When sending a compute request via `/execute`, you can specify a routing strategy in the `X-Zakuro-Requirements` header. The strategy determines how the broker selects which worker handles the request.
| `best_price` | `price`, `cheap`, `cheapest` | Selects the worker with the lowest estimated cost. **(Default)** |
| `best_latency` | `latency`, `fast`, `fastest` | Selects the worker with the lowest average response time. |
| `best_availability` | `availability`, `available` | Selects the worker with the most available resources and fewest active requests. |
| `round_robin` | `robin`, `rr` | Distributes requests evenly across all healthy workers in order. |
| `random` | `rand` | Selects a random healthy worker. |
| `weighted_capacity` | `weighted`, `capacity` | Randomly selects a worker weighted by available CPU and memory. |
### Worker Scoring Algorithm
For the default `best_price` strategy, the overall worker score is computed as:
```
score = price_score * 0.5 + load_score * 0.3 + latency_score * 0.2
```
Where:
- **Price score (50%):** `cpu_price * 0.6 + memory_price * 0.3 + gpu_price * 0.1`
- **Load score (30%):** `(1 - cpus_available/total + 1 - memory_available/total) / 2`
- **Latency score (20%):** `avg_latency_ms / 100`
Lower scores indicate better workers.
---
## Benchmarking
`zc bench` measures the broker's throughput and latency under load.
### Basic Benchmark
```bash
# Default: 10 concurrent workers, 1000 requests, best_price strategy
zc bench
# Benchmark with a specific strategy
zc bench -s best_latency
# High-load benchmark
zc bench -c 100 -n 10000
# Benchmark a remote broker
zc bench http://broker.tailnet:9000
```
The benchmark:
1. Verifies broker connectivity via `/health`
2. Checks available workers via `/workers`
3. Sends requests concurrently to `/execute`
4. Displays a progress spinner during execution
5. Prints a detailed report with throughput and latency percentiles
**Example output:**
```
═══════════════════════════════════════════════
◆ Benchmark Results
═══════════════════════════════════════════════
Summary
────────────────────────────────
Total Requests: 1000
Successful: 987 (98.7%)
Failed: 13 (1.3%)
Duration: 4.21s
Throughput: 237.53 req/s
Latency (ms)
────────────────────────────────
Min: 0.84
Avg: 3.21
Max: 42.10
p50: 2.15
p75: 3.80
p90: 6.20
p95: 9.45
p99: 28.30
Latency Histogram
────────────────────────────────
≤1ms [██████ ] 120 ( 12.2%)
≤5ms [██████████████████████████████] 650 ( 65.9%)
≤10ms [████████████ ] 150 ( 15.2%)
≤25ms [██ ] 40 ( 4.1%)
≤50ms [█ ] 27 ( 2.7%)
```
### Strategy Comparison
Compare all six routing strategies side by side:
```bash
zc bench --compare
zc bench --compare -c 50 -n 5000
```
This runs the benchmark separately for each strategy and outputs a comparison table highlighting the best throughput and lowest latency.
### Benchmark Options
| `--concurrency` | `-c` | `10` | Number of concurrent workers |
| `--requests` | `-n` | `1000` | Total requests to send |
| `--url` | `-u` | `http://127.0.0.1:9000` | Broker URL |
| `--user` | | `bench-user` | User ID for requests |
| `--strategy` | `-s` | `best_price` | Routing strategy to test |
| `--compare` | | | Compare all strategies |
| `--list-strategies` | | | List available strategies |
| `--help` | `-h` | | Show help |
---
## Remote Monitoring
Attach to a running broker from another machine to view its TUI dashboard:
```bash
# Attach to a local broker
zc attach localhost:9000
# Attach to a remote broker on the Tailscale network
zc attach http://broker.tailnet:9000
```
The remote TUI connects to the broker's `/stats` endpoint and refreshes every 500ms. It provides the same interface as the local TUI, plus:
| `r` | Force refresh |
---
## System Diagnostics
View system information, network status, and detected compute clusters:
```bash
zc info
```
**Output sections:**
| **Network** | Hostname, operating mode (Tailscale P2P / Local), local IP, Tailscale IP |
| **Compute Clusters** | Availability of Ray, Dask, and Spark clusters with version and node count |
| **Environment** | Status of `ZAKURO_API_KEY`, `ZAKURO_MASTER_KEY`, `DATABASE_URL`, `TAILSCALE_AUTHKEY` (secrets are masked) |
| **Broker Ports** | Connectivity status for Broker API (9000), Worker (3960), Ray (10001), Dask (8786), Spark (7077), PostgreSQL (5432) |
---
## Credit System
The broker uses a credit-based billing system to charge users for compute requests.
### PostgreSQL Ledger
The broker uses PostgreSQL as the single source of truth for credit balances. For security, it connects as a restricted `zakuro_broker` role that has **zero direct table access** — all operations go through **11 SECURITY DEFINER stored procedures**:
| `broker_auth_api_key(key_hash)` | Authenticate API key → returns zakuro_user_id |
| `broker_resolve_user_id(zakuro_uid)` | Resolve PG users.id from zakuro_user_id |
| `broker_get_balance(zakuro_uid)` | Get credit balance |
| `broker_reserve_credits(amount, zakuro_uid)` | Atomic credit reservation |
| `broker_refund_credits(amount, zakuro_uid)` | Refund credits (commit/cancel) |
| `broker_insert_transaction(...)` | Insert transaction record (triggers PG NOTIFY) |
| Plus 5 more for transaction state management, balance snapshots, and user lookup |
**Connection pooling:** r2d2_postgres pool (max 8 connections) for high concurrency.
**PG triggers:** `trg_notify_transaction` (INSERT) + `trg_notify_credit_update` (UPDATE credits_balance) broadcast changes to dashboard via LISTEN/NOTIFY.
This ensures the broker cannot access `email`, `hashed_password`, `role`, or any other user columns, and cannot query balances across users.
### Reserve-Commit Model
Each compute request follows a reserve-commit flow:
1. **Reserve:** The estimated cost is atomically deducted from the user's balance and stored as a reservation (5-minute TTL).
2. **Execute:** The request is forwarded to the selected worker.
3. **Commit:** On success, the reservation is finalized with the actual cost, and any difference is refunded. On failure, the full amount is refunded (cancel).
This ensures users are never overcharged and credits are not lost on failures.
### API Keys
- **Master Key:** Set via `ZAKURO_MASTER_KEY`. Has full permissions (admin operations like adding credits, viewing ledger status).
- **User API Keys:** Follow the `zk_{user_id}_{hex}` format. The broker extracts the `user_id` from the key format for billing. In PG mode, keys are also verified via the `broker_auth_api_key` stored procedure.
- **Storage:** In PG mode, API keys are stored as SHA256 hashes in the `api_keys` table (accessed only via stored procedures). In API mode, key resolution uses the key format or the dashboard API.
### Local Fallback
When PostgreSQL is unavailable (`allow_local_fallback: true` by default), the broker falls back to in-memory credit management. Balances are lost on restart.
### Local Mode (Free Execution)
When running in local discovery mode (no Tailscale), all executions are free and no credits are charged.
### Per-Worker Local Detection (P2P Mesh)
In a multi-node P2P mesh, each broker detects its own Tailscale IP at startup and uses it to distinguish local vs remote workers:
- **Local workers** (at `127.0.0.1` or matching the broker's own Tailscale IP) execute for **free** — no credit reservation or billing
- **Remote workers** (on other Tailscale IPs) are **charged** via the normal reserve-commit flow
This enables a mesh topology where each node acts as both broker and worker. When `best_price` routing is used, the local worker is always preferred (cost=0). Other strategies like `round_robin` distribute across local and remote workers.
### P2P Broker-to-Broker Communication
Enable distributed broker mesh with `ZAKURO_P2P=true`. This mode adds broker-to-broker communication for credit operations, eliminating PostgreSQL calls on the hot path for authoritative users.
**Architecture:**
Each user is assigned an **authoritative broker** using a consistent hash (FNV-1a):
```
authority_index = hash(zakuro_user_id) % num_brokers
```
**Authority modes:**
| **Authority::Local** | User owned by this broker | **Zero PG** | DashMap in-memory credit ops (reserve, commit, balance) |
| **Authority::Peer(url)** | User owned by remote broker | **~1ms HTTP** | Forward credit ops to authoritative broker via `/peer/*` endpoints |
| **Authority::Standalone** | Peer broker unreachable | **API / PG fallback** | Ledger API or PostgreSQL path for resilience |
**Transaction flow (P2P authoritative):**
1. Auth (DashMap cache) → 2. Balance (DashMap) → 3. Reserve (DashMap atomic deduct)
4. WAL: RESERVED → 5. Forward to worker → 6. WAL: EXECUTED
7. Commit (DashMap refund diff) → 8. Queue in flush buffer → 9. WAL: COMMITTED → 10. Return
**Transaction flow (P2P non-authoritative):**
1. Auth (DashMap cache) → 2. HTTP `/peer/reserve` to authoritative (~1ms)
3. WAL: RESERVED → 4. Forward to worker → 5. WAL: EXECUTED
6. HTTP `/peer/commit` to authoritative (~1ms) → 7. WAL: COMMITTED → 8. Return
**Peer endpoints:**
All peer endpoints require `X-Peer-Key` header authentication:
| `/peer/health` | GET | Peer broker health check |
| `/peer/reserve` | POST | Reserve credits for remote user (body: `{user_id, amount, reservation_id}`) |
| `/peer/commit` | POST | Commit transaction (body: `{reservation_id, actual_cost, worker_id, duration_ms}`) |
| `/peer/cancel` | POST | Cancel reservation (body: `{reservation_id}`) |
| `/peer/earn` | POST | Credit earnings to broker owner (body: `{request_id, amount}`) |
| `/peer/balance` | GET | Get balance (query: `?user_id=...`) |
| `/peer/workers` | GET | List this broker's local workers |
**Flush thread:**
In P2P mode, authoritative brokers maintain a `TransactionBuffer` that batches writes every **5 seconds** (every health-check tick) via the dashboard API or PostgreSQL:
- Pending transactions (all state transitions)
- Balance snapshots (for dashboard/audit)
This decouples the hot path from PostgreSQL latency while ensuring eventual consistency.
**Environment variables:**
| `ZAKURO_P2P` | No | `false` | Enable P2P mode |
| `ZAKURO_PEER_KEY` | Yes (in P2P) | — | Shared secret for `X-Peer-Key` auth |
| `ZAKURO_OWNER_ID` | Yes (in P2P) | — | This broker's unique ID (used in authority hash) |
| `ZAKURO_PEERS` | Yes (in P2P) | — | Comma-separated peer broker URLs: `http://10.13.13.5:9000,http://10.13.13.6:9000` |
**Resilience:**
- If authoritative peer is down → `Authority::Standalone` → Ledger API or PostgreSQL fallback
- WAL replay on crash: `reserved→cancel`, `executed→commit` (same as before)
- Peer discovery via `ZAKURO_PEERS` + health probes on startup
---
## Write-Ahead Log (WAL)
The broker maintains a write-ahead log for crash recovery. Candidate paths (first writable wins): `ZAKURO_WAL_PATH` env → `$HOME/.zakuro/wal.jsonl` → `/tmp/zakuro-wal.jsonl`. Each transaction progresses through three states:
| `Reserved` | Credits reserved, request sent to worker |
| `Executed` | Worker returned successfully, actual cost calculated |
| `Committed` | Credits committed (or refunded on failure) |
**Implementation:**
- **Append-only JSONL file** — one JSON object per line, never modified in-place
- **DashMap in-memory index** — O(1) lookup by `request_id` for status checks and updates
- **Batched fsync** — writes are flushed every **100ms** OR after **64 entries** (whichever comes first)
- **Thread-safe** — `Arc<RwLock<BufWriter>>` for concurrent writes from multiple request handlers
This design provides durability without sacrificing performance: the batched fsync amortizes disk I/O cost across multiple transactions.
### Crash Recovery
On startup, the broker replays any uncommitted WAL entries via `recovery.rs`:
- **Reserved** entries: credits are refunded (worker may have crashed before responding)
- **Executed** entries: credits are committed at the actual cost (worker succeeded but broker crashed before committing)
**P2P mode:** WAL replay works seamlessly with P2P — authoritative brokers apply state changes to their DashMap, non-authoritative brokers skip or retry peer calls.
The WAL is **automatically compacted** every ~5 minutes: committed and failed entries are removed from disk, keeping only uncommitted entries. The in-memory index is also cleared of completed entries.
### WAL Format
Append-only JSONL file. Each line is a JSON object:
```json
{"request_id":"uuid","user_id":"user","reservation_id":"res-id","estimated_cost":0.001,"actual_cost":0.0008,"worker_id":"worker-uuid","duration_ms":250.0,"timestamp":"2024-01-01T00:00:00Z","status":"Committed"}
```
---
## P2P Mesh Deployment
Deploy a multi-node mesh where each node runs a broker + worker pair connected via Tailscale.
### Architecture
```
Node 1 (zk0-node01) Node 2 (zk0-node02)
┌─────────────────────┐ ┌─────────────────────┐
│ Tailscale sidecar │◄══ Tailscale ════►│ Tailscale sidecar │
│ (shared netns) │ mesh │ (shared netns) │
│ │ │ │
│ Broker (:9000) │ │ Broker (:9000) │
│ ├ PostgreSQL │◄══ shared PG ════► ├ PostgreSQL │
│ ├ WAL + DashMap │ (optional) │ ├ WAL + DashMap │
│ └ Discovery │ │ └ Discovery │
│ │ │ │
│ Worker (:3960) │ │ Worker (:3960) │
│ type: standard │ │ type: premium │
│ (free to self) │ │ (free to self) │
└─────────────────────┘ └─────────────────────┘
```
Each node's Tailscale sidecar and worker share the broker's network namespace via Docker `network_mode: "service:..."`. All three containers get the same Tailscale IP.
### Prerequisites
- Two Tailscale auth keys (one per node)
- Docker and Docker Compose
### Quick Start
```bash
cd zak-zakuro/docker
# Set environment variables
export ZK0NODE01_API_KEY=tskey-auth-...
export ZK0NODE02_API_KEY=tskey-auth-...
export NODE1_TAILSCALE_IP=100.x.x.x # from `tailscale ip -4` on node1
export NODE2_TAILSCALE_IP=100.y.y.y # from `tailscale ip -4` on node2
# Start the 2-node mesh
docker compose -f docker-compose.mesh.yml up -d --build
```
### Verification
```bash
# Both brokers should see 2 workers each
curl http://localhost:9001/workers # node1: local standard + remote premium
curl http://localhost:9002/workers # node2: local premium + remote standard
# Check credits
curl http://localhost:9001/credits/node1-user
# Run the demo
python docker/mesh-demo.py http://localhost:9001 node1-user
```
### How Discovery Works in the Mesh
1. Each broker reads `ZAKURO_TAILSCALE_IP` and `ZAKURO_PEERS` environment variables
2. Discovery scans localhost ports first (finds local worker)
3. Then probes explicit peers (finds remote workers via Tailscale)
4. CGNAT subnet scanning (100.x.x.x) is skipped — Tailscale IPs are not on contiguous /24s
5. Existing workers get heartbeat refreshed via TCP-connect only (no full HTTP probe needed)
6. Worker deduplication by name prevents the same worker appearing twice
### Environment Variables (Mesh)
| `ZAKURO_TAILSCALE_IP` | This node's Tailscale IP (for local worker detection) |
| `ZAKURO_PEERS` | Comma-separated `ip:port` of remote workers to probe |
| `NODE1_TAILSCALE_IP` | Node 1's Tailscale IP (used in compose) |
| `NODE2_TAILSCALE_IP` | Node 2's Tailscale IP (used in compose) |
| `ZK0NODE01_API_KEY` | Tailscale auth key for node 1 |
| `ZK0NODE02_API_KEY` | Tailscale auth key for node 2 |
---
## Cluster Commands
| `zc up` | Start 1 worker + broker locally (foreground) |
| `zc up --workers N` | Start N workers + broker (ports 3960–3960+N) |
| `zc up --workers N -d` | Start N workers + broker in the background (daemon) |
| `zc down` | Stop all workers + broker started by `zc up` |
| `zc workers` | List workers registered with the local broker |
| `zc me` / `zc credits` | Show authenticated user info and credits |
| `zc attach <url>` | Attach remote TUI to a running broker |
| `zc update` | Update the `zc` CLI from `get.zakuro-ai.com` |
| `zc pull` | Pull updated Docker images |
| `zc images` | List Zakuro images built on the machine |
| `zc ps` | List currently running Zakuro containers |
| `zc kill` | Stop and remove running Zakuro containers |
| `zc restart` | Restart containers with updated images |
---
## Environment Variables
| `ZAKURO_API_KEY` | Yes | — | Authentication token. Also used for dashboard API calls in API mode. |
| `ZAKURO_API_URL` | No | — | Dashboard API URL (e.g., `https://my.zakuro-ai.com`). When set with `ZAKURO_API_KEY`, enables API mode (no direct PG access). |
| `ZAKURO_MASTER_KEY` | No | — | Master API key for admin operations (adding credits, ledger status). |
| `DATABASE_URL` | No | `postgresql://zakuro_broker:broker_secret_change_me@localhost:5432/zakuro` | PostgreSQL connection URL (restricted broker role). Ignored in API mode. |
| `ZAKURO_WORKER_KEY` | No | — | Shared secret for worker management endpoints (`POST/DELETE /workers`, heartbeat). When unset, these endpoints are open. |
| `TAILSCALE_AUTHKEY` | No | — | Tailscale auth key. Automatically retrieved from dashboard API when `ZAKURO_API_URL` is set. |
| `ZAKURO_TAILSCALE_IP` | No | — | Override Tailscale IP detection (for Docker sidecar setups). |
| `ZAKURO_PEERS` | No | — | Comma-separated `ip:port` peers for worker discovery, or `http://host:port` for broker peers in P2P mode. |
| `ZAKURO_P2P` | No | `false` | Enable P2P broker-to-broker communication. |
| `ZAKURO_PEER_KEY` | No (Yes in P2P) | — | Shared secret for P2P broker authentication (`X-Peer-Key` header). |
| `ZAKURO_OWNER_ID` | No (Yes in P2P) | — | This broker's unique ID for authority assignment (FNV-1a hash). Auto-derived from `ZAKURO_API_KEY` if in `zk_{user}_{hex}` format. |
| `ZAKURO_NODE_NAME` | No | Auto-detected | Human-readable node name (Tailscale hostname → system hostname). |
| `ZAKURO_WORKER_PORT` | No | `3960` | Primary port workers listen on (discovery). |
| `ZAKURO_SCAN_RANGE` | No | — | Port range for discovery scan, e.g. `3960-3999`. |
| `ZAKURO_SCAN_INTERVAL` | No | `15` | Seconds between discovery scans. |
---
## Configuration Reference
### BrokerConfig
| `host` | `0.0.0.0` | Host to bind the broker server |
| `port` | `9000` | Port to bind the broker server |
| `health_check_interval` | `5` | Worker health check interval in seconds |
| `worker_timeout` | `30` | Seconds before marking a worker unhealthy |
| `min_credits` | `0.001` | Minimum credits required for any request |
| `daemon` | `false` | Run in daemon (background) mode |
| `verbose` | `true` | Show live transaction logs (foreground mode) |
| `tui_mode` | `false` | Enable interactive TUI dashboard |
| `enable_discovery` | `true` | Enable automatic worker discovery |
### DiscoveryConfig
| `subnet` | `10.13.13` | Tailscale subnet to scan |
| `worker_port` | `3960` | Primary port workers listen on (from `ZAKURO_WORKER_PORT` env) |
| `extra_ports` | `[3961, 3962]` | Additional ports to scan |
| `scan_port_range` | `None` | Full port range scan, e.g. `(3960, 3999)` (from `ZAKURO_SCAN_RANGE` env) |
| `interval_secs` | `15` | Seconds between discovery scans (from `ZAKURO_SCAN_INTERVAL` env) |
| `enable_scan` | `true` | Enable active network scanning |
| `enable_dns` | `true` | Enable DNS-based discovery |
| `peers` | `[]` | Explicit peer addresses from `ZAKURO_PEERS` env var |
### LedgerConfig
| `database_url` | `postgresql://zakuro_broker:broker_secret_change_me@localhost:5432/zakuro` | PostgreSQL connection URL (restricted broker role) |
| `master_key` | `None` | From `ZAKURO_MASTER_KEY` env |
| `default_credits` | `0.0` | Credits for new users (no free credits by default) |
| `allow_local_fallback` | `true` | Fall back to in-memory when PostgreSQL is unavailable |
| `api_url` | `None` | From `ZAKURO_API_URL` env — dashboard API URL |
| `api_key` | `None` | From `ZAKURO_API_KEY` env — when both `api_url` and `api_key` are set, the broker runs in API mode (PG pool is never created) |
### Worker Pricing Model
Workers advertise their pricing when registering:
| `cpu_price` | Cost per CPU-core per second |
| `memory_price` | Cost per GiB of memory per second |
| `gpu_price` | Cost per GPU per second |
| `min_charge` | Minimum charge per request |
The estimated cost for a request is:
```
cost = max(
cpu_price * cpus * duration_secs +
memory_price * memory_gib * duration_secs +
gpu_price * gpus * duration_secs,
min_charge
)
```