The Orchestrator
The backend worker that manages key unsealing, dataset decryption, and 0G Compute dispatch.
The Orchestrator
The Orchestrator (packages/orchestrator) is the most security-sensitive component in the current demo stack. It coordinates access grants, key envelopes, simulated 0G Compute dispatch, job tracking, and settlement.
The long-term production goal is different: the Orchestrator should become a coordinator, not a decryption point. Dataset keys should be released only to a 0G-compatible confidential compute node after that node presents valid TEE attestation.
It runs as a long-running Node.js process — not a serverless function. This is intentional: the Orchestrator maintains persistent database connections and an in-memory job tracker.
Core Responsibilities
- Polling — queries Envio HyperIndex every 5 seconds for new
Grantedjobs - Key envelope coordination — fetches sealed envelopes and enforces that key handling only happens after on-chain grant verification
- Demo compute simulation — mirrors the 0G Compute lifecycle used by the product UI and contracts
- Future compute dispatch — target integration with a 0G-compatible confidential provider node
- Job tracking — polls 0G task status and drives on-chain lifecycle forward
- Settlement — calls
confirmTrainingComplete()ormarkJobFailed()based on 0G task outcome
Current Demo Dispatch Pipeline
When a Granted job is found, dispatcher.ts executes one of two branches. In demo mode, storage/decryption/real compute are skipped and the job tracker simulates the 0G Compute lifecycle. The legacy non-demo branch is documented below because it explains why the production architecture must move to attestation-gated key release:
1. Verify on-chain state = Granted (DataPolicy.jobs(jobId).state)
↓
2. Fetch sealed envelope from Web App API
GET /api/orchestrator/key-envelope?datasetRoot=0x...
↓
3. ECIES-decrypt envelope → AES key (in memory)
↓
4. Download encrypted dataset from 0G Storage (Indexer.download)
↓
5. AES-256-GCM decrypt in memory → plaintext JSONL
↓
6. Write to /tmp/licen-plain-<jobId>.jsonl (mode 0o600)
↓
7. ZgFile.fromFilePath() + Indexer.upload() → plaintextRootHash
↓
8. Delete temp file (finally block)
Zero AES key bytes (finally block)
↓
9. broker.fineTuning.createTask(provider, model, plaintextRootHash, config)
→ { taskId, providerAddress }
↓
10. Insert into compute_jobs DB table
↓
11. startJob(jobId) on-chain → state: RunningIf any step from 1–9 fails: markJobFailed(jobId, reason) is called automatically. The researcher is refunded.
This legacy pipeline is intentionally not the final privacy architecture. The hackathon demo uses simulation for the compute leg, while this branch documents the integration gap: today's public fine-tuning interface does not provide encrypted dataset key release into the provider TEE.
Planned 0G-Compatible Confidential Compute Pipeline
The production target is a provider node compatible with the 0G Compute experience but extended with encrypted dataset input and attestation-gated key release.
1. Verify on-chain state = Granted
↓
2. Select a LICEN/0G-compatible confidential provider
↓
3. Provider boots training container inside TEE/CVM
↓
4. Provider generates:
- remote attestation quote
- ephemeral public key generated inside the TEE
- container image hash
- training code hash
↓
5. LICEN verifies the quote and release policy
↓
6. Dataset AES key is encrypted to the TEE ephemeral public key
↓
7. Provider TEE downloads encrypted dataset from 0G Storage
↓
8. Provider TEE decrypts dataset internally and runs fine-tuning
↓
9. Provider TEE encrypts model artifact and uploads it to 0G Storage
↓
10. Provider signs result manifest:
datasetRoot, resultHash, codeHash, attestationRef, actualEpochs
↓
11. DataPolicy.confirmTrainingComplete() settles royaltiesIn this model, neither the web server nor the Orchestrator receives plaintext dataset bytes. The dataset key becomes usable only by a measured training runtime inside the confidential provider node.
Why This Is Needed
The public 0G fine-tuning flow currently accepts a dataset path or dataset root hash. That is not enough for LICEN's strongest privacy claim because a normal dataset root hash implies the compute provider can retrieve the training file directly. LICEN needs the provider runtime itself to understand encrypted datasets and to receive the key only after attestation.
The planned node closes that gap while still building on 0G primitives:
- 0G Storage for encrypted dataset and encrypted model artifact roots
- 0G Chain for access policy, escrow, settlement, and audit references
- 0G-compatible compute semantics for provider discovery, task lifecycle, and fine-tuning UX
- TEE attestation and key release for confidential input handling
Job Tracker
jobTracker.ts runs a setInterval loop every OG_TASK_POLL_MS (default 3,000ms in demo mode), polling task status for all active jobs.
0G Task Status → On-Chain Action
| 0G Status | Orchestrator Action | On-chain State |
|---|---|---|
Init, SettingUp, Training | Wait | Running |
Delivered | acknowledgeModel() (provider fee settlement) | Running |
Finished | confirmTrainingComplete(jobId, actualEpochs, resultHash, attestationRef) | Completed |
Failed | markJobFailed(jobId, "0G_COMPUTE_TASK_FAILED") | Failed |
Restart safety: On startup, jobTracker queries the DB for all running or acknowledging jobs and resumes polling them. No jobs are lost on crash/restart.
Environment Variables
# ECIES key custody — the Orchestrator's identity
ORCHESTRATOR_PRIVATE_KEY=<hex> # secp256k1 private key
# On-chain settlement wallet
BACKEND_WALLET_PRIVATE_KEY=<hex> # Must be the backendWallet on DataPolicy.sol
# 0G Compute funding wallet
OG_COMPUTE_PRIVATE_KEY=<hex> # Wallet holding 0G tokens for compute fees
# Web App API auth
ORCHESTRATOR_API_SECRET=<secret> # Shared secret for /api/orchestrator/* routes
# Database (Neon PostgreSQL, unpooled)
DATABASE_URL=<neon-unpooled-url>
# Optional overrides
OG_COMPUTE_PROVIDER_ADDRESS= # Pin to specific provider; unset = auto-discover
OG_COMPUTE_MODEL=Qwen2.5-0.5B-Instruct
OG_INDEXER_RPC_URL=https://indexer-storage-testnet-standard.0g.ai
OG_TASK_POLL_MS=30000Running Locally
cd packages/orchestrator
cp .env.example .env
# Fill in all required keys above
pnpm install
pnpm devThe Orchestrator logs every action to stdout with structured JSON. Watch for:
[poller] Found N granted jobs— new jobs detected[dispatcher] Unsealing key for datasetRoot=0x...— key exchange starting[computeClient] Task dispatched: taskId=...— 0G Compute job created[jobTracker] Job completed: jobId=...— settlement executed