HIP-38: Admin Console Standard. Status Draft. Hanzo architectural specification.
Hanzo Console is the administrative dashboard for Hanzo platform operators, serving production traffic at console.hanzo.ai. It provides a unified interface for managing organizations, users, projects, API keys, quotas, billing oversight, and infrastructure health across the entire Hanzo ecosystem.
Console is a clean-room implementation on @hanzo/gui over the unified /v1 backend, with multi-organization administration, IAM integration (HIP-26), KMS secret management (HIP-0027), and operator-grade access controls.
This HIP still says Console is a Langfuse fork; it is not. The repository's
NOTICEis explicit: the Observe surface reproduces the screen layout and user flows of Langfuse's observability views, wired to the native Hanzo Cloud/v1/evalscontract, and no Langfuse source code is used — the one remaining mention of Langfuse in the whole repository is that attribution. The first commit is "Hanzo Cloud Console (console2) on@hanzo/guiover the unified/v1backend". Every passage below that reasons about forking, tracking upstream, or merging upstream releases describes a relationship that does not exist, and must be rewritten before this HIP can be Final.
The distinction between Console and Cloud is fundamental to Hanzo's architecture. Cloud (cloud.hanzo.ai) is the customer-facing product where teams manage their AI workloads. Console is the operator-facing product where Hanzo administrators manage the platform itself. This separation follows the control plane vs. management plane pattern established by cloud infrastructure providers.
Repository: github.com/hanzoai/console Port: 3000 Docker: ghcr.io/hanzoai/console:latest
A platform serving multiple organizations (Hanzo, Lux, Zoo, Pars) across multiple products (Cloud, Chat, Commerce, Platform) generates operational complexity that no individual service dashboard can address:
Console provides a single pane of glass for all operator tasks. It proxies to IAM for identity operations, KMS for secret management, Cloud for workload monitoring, and LLM Gateway for usage analytics. Operators never touch databases directly. Every action is authenticated, authorized, and logged.
This section explains the reasoning behind Console's architecture. These decisions are interconnected -- changing one would cascade into the others.
Cloud and Console serve fundamentally different audiences with different trust levels and different operational needs.
Cloud is a customer-facing product. A team at an AI startup uses Cloud to manage their LLM deployments, monitor costs, and evaluate model performance. Cloud users see only their own organization's data. The UI is designed for ease of use, onboarding, and self-service. Trust level: authenticated external users.
Console is an operator-facing product. A Hanzo engineer uses Console to manage the platform itself -- creating organizations, adjusting quotas, investigating billing anomalies, monitoring infrastructure health. Console users see data across all organizations. The UI is designed for power users who need complete visibility. Trust level: internal administrators with elevated privileges.
This mirrors the pattern in cloud infrastructure:
Combining these into a single application creates a permission and UX problem. Either the UI is cluttered with admin controls that 99% of users should never see, or the permission model is so complex that a misconfiguration could expose operator tools to customers. Separating them at the application level makes the security boundary explicit and the UX clean.
Hanzo is not a single-product company. It operates four brands:
| Organization | Domain | Purpose | |-------------|--------|---------| | Hanzo | hanzo.ai | AI infrastructure and services | | Lux | lux.network | Blockchain network and validators | | Zoo | zoo.ngo | Decentralized AI research foundation | | Pars | pars.ai | Regional AI platform |
Each organization has its own users, billing, API keys, branding, and compliance requirements. A Lux validator operator should never see Hanzo AI billing data. A Zoo researcher should not have access to Pars API keys.
But the infrastructure is shared. All four organizations run on the same Kubernetes clusters, use the same IAM instance, share the same KMS. An operator managing this infrastructure needs to work across all organizations in a single session.
Console's multi-org bootstrap solves this at startup:
# Environment variables for multi-org provisioning
HANZO_INIT_ORG_IDS=hanzo,lux,zoo,pars
HANZO_INIT_ORG_NAMES="Hanzo,Lux Network,Zoo Labs,Pars"
[email protected]
HANZO_INIT_PROJECT_ORG_ID=hanzo
On first boot, Console calls IAM to create or upsert all four organizations, grants OWNER membership to the bootstrap user, and creates default projects and API keys for the primary org. This means a fresh deployment goes from zero to fully multi-org in a single startup, not a series of manual steps.
The alternative -- adding organizations one at a time through a UI -- is both slow and error-prone. It also introduces a chicken-and-egg problem: you need an organization to log in, but you need to log in to create an organization. Bootstrap solves this.
Users can belong to multiple organizations with different roles. This is a deliberate design choice that reflects how real teams work.
Consider [email protected]:
hanzo organization (full admin access)lux organization (can manage users and projects)zoo organization (can view data but not change settings)The three-tier role hierarchy:
| Role | Capabilities | |------|-------------| | OWNER | Full control: create/delete projects, manage billing, invite/remove users, change org settings, delete the organization | | ADMIN | Operational control: manage projects, invite users, view billing, manage API keys | | MEMBER | Read access: view projects, traces, evaluations; use API keys assigned to their projects |
This is simpler than fine-grained RBAC (which Hanzo IAM supports but Console does not expose). Three roles cover 95% of real-world access patterns. Adding custom roles would increase the permission surface area without proportional benefit. If a specific permission is needed (e.g., "can view billing but not traces"), the answer is to create a separate project with appropriate visibility, not to add another role.
Console does not own data. It is a management proxy that delegates to specialized services:
Console (console.hanzo.ai)
|
|-- IAM (hanzo.id, HIP-26)
| |-- Organization CRUD
| |-- User management
| |-- Role/membership management
| |-- Balance queries
| |-- OAuth application management
|
|-- KMS (api.hanzo.ai/v1/kms, HIP-0027)
| |-- Secret creation and rotation
| |-- Project-scoped secret access
| |-- Audit log retrieval
|
|-- LLM Gateway (llm.hanzo.ai, HIP-4)
| |-- Usage metrics per org/project/user
| |-- Model performance data
| |-- Cost tracking
|
|-- Cloud (cloud.hanzo.ai)
|-- Deployment status
|-- Resource utilization
|-- Service health
This proxy architecture means Console has no persistent state of its own beyond session data. If Console goes down, the underlying services continue operating. If Console needs to be redeployed, there is no data migration -- just restart the container.
Console provides full lifecycle management for organizations:
interface Organization {
id: string; // Unique identifier
name: string; // URL-safe slug (e.g., "hanzo")
displayName: string; // Human-readable name (e.g., "Hanzo AI")
websiteUrl: string; // Organization website
logoUrl?: string; // Custom branding
themeData: {
themeType: "dark" | "light";
colorPrimary: string; // Hex color (e.g., "#fd4444")
};
passwordType: string; // Hash algorithm (argon2id)
defaultApplication: string; // Default OAuth app
createdTime: string; // ISO 8601
memberCount: number; // Computed from memberships
}
Operations:
POST /v1/iam/add-organization. Automatically creates default OAuth application and certificate.POST /v1/iam/update-organization. Supports branding, password policy, and MFA settings.GET /v1/iam/get-organizations. Filtered by operator's membership.POST /v1/iam/delete-organization. Requires OWNER role. Cascades to applications, users (membership only), and projects.interface UserMembership {
userId: string;
organizationId: string;
role: "OWNER" | "ADMIN" | "MEMBER";
joinedAt: string;
invitedBy?: string;
}
Operations:
Projects are the primary resource isolation unit within an organization. Each project has its own API keys, quotas, traces, and evaluations.
interface Project {
id: string;
name: string;
organizationId: string;
createdAt: string;
updatedAt: string;
settings: {
defaultModel?: string; // Default LLM for this project
maxTokensPerRequest?: number;
monthlyBudget?: number; // USD budget cap
rateLimitRpm?: number; // Requests per minute
};
apiKeys: APIKey[];
}
interface APIKey {
id: string;
projectId: string;
name: string;
keyPrefix: string; // First 8 chars for identification
hashedKey: string; // Stored hashed, never in plaintext
scopes: string[]; // e.g., ["traces:read", "traces:write"]
createdAt: string;
lastUsedAt?: string;
expiresAt?: string;
}
Operations:
Console inherits Langfuse's tracing and evaluation capabilities, extended for multi-org use:
Console aggregates health signals from all Hanzo services:
interface ServiceHealth {
name: string; // e.g., "iam", "kms", "llm-gateway"
url: string; // Health check endpoint
status: "healthy" | "degraded" | "down";
latencyMs: number;
lastChecked: string;
details?: Record<string, any>; // Service-specific metadata
}
Monitored services: | Service | Health Endpoint | Expected Response | |---------|----------------|-------------------| | IAM | https://hanzo.id/healthz | {"status": "ok"} | | KMS | https://api.hanzo.ai/v1/kms/healthz | {"status": "ok"} | | LLM Gateway | https://llm.hanzo.ai/health | {"status": "ok"} | | Cloud | https://cloud.hanzo.ai/healthz | {"status": "ok"} | | Console | https://console.hanzo.ai/healthz | {"status": "ok"} |
Console provides operator-level billing visibility by aggregating data from IAM balances and Cloud usage:
Every administrative action in Console generates an audit entry:
interface AuditEntry {
id: string;
timestamp: string; // ISO 8601
actor: {
userId: string;
email: string;
role: string;
ipAddress: string;
userAgent: string;
};
action: string; // e.g., "org.update", "user.invite", "apikey.create"
resource: {
type: string; // e.g., "organization", "user", "project"
id: string;
name: string;
};
organizationId: string;
details: Record<string, any>; // Action-specific metadata
result: "success" | "failure";
errorMessage?: string;
}
Audit logs are queryable by time range, actor, action type, resource, and organization. They are immutable -- once written, they cannot be modified or deleted, even by OWNER users. Retention is configurable per organization (default: 90 days).
Per-organization configuration managed through Console:
Internet
|
+---------+---------+
| Hanzo Ingress |
| (TLS termination) |
+---------+---------+
|
console.hanzo.ai
|
+-------------+-------------+
| Hanzo Console |
| (Next.js 14) |
| :3000 |
+---+------+------+----+----+
| | | |
+------+ +---+--+ +--+--+ +-------+
| IAM | | KMS | | LLM | | Cloud |
|:8000 | |:8080 | |:4000| | :8000 |
+--+---+ +--+---+ +--+--+ +---+---+
| | | |
+-----+-----+ +--+---+ +-+------+ +-+------+
| the shared | | KMS | | egress | |Workers |
| tier | |store | | HIP-143| |Queues |
| HIP-0144 | +------+ +--------+ +--------+
+-----------+
| Layer | Technology | Rationale | |-------|-----------|-----------| | Frontend | Next.js 14 (Pages Router) | SSR for admin dashboards, tRPC for type safety | | UI | shadcn/ui (Radix primitives) + Tailwind CSS | Consistent with Hanzo design system | | API | tRPC (internal) + REST (public) | Type-safe internal APIs, standard REST for integrations | | State | the shared tier at the rank HIP-0144 sets | projects, traces, evaluations | | Analytics | ClickHouse | High-volume trace data, fast aggregation queries | | Cache/Queue | the one shared KV | session cache, background job processing | | Storage | MinIO (S3-compatible) | File attachments, exported reports | | Auth | NextAuth.js with IAM provider | Delegates to hanzo.id for OAuth |
Console runs on the hanzo-k8s DOKS cluster at 24.199.76.156:
# compose.prod.yaml (simplified)
services:
console-web:
image: ghcr.io/hanzoai/console:latest
ports:
- "3000:3000"
environment:
# Console holds an identity, not a database and not a key.
IAM_ENDPOINT: https://hanzo.id
IAM_CLIENT_ID: hanzo-console
IAM_CLIENT_SECRET: ${IAM_CLIENT_SECRET} # KMS reference: hanzo/console/…@prod
EGRESS_ADDRESS: egress.hanzo.ai:9653 # every outbound call (HIP-0143)
HANZO_INIT_ORG_IDS: hanzo,lux,zoo,pars
HANZO_INIT_ORG_NAMES: "Hanzo,Lux Network,Zoo Labs,Pars"
HANZO_INIT_USER_EMAIL: [email protected]
HANZO_INIT_PROJECT_ORG_ID: hanzo
S3_ENDPOINT: ${S3_ENDPOINT}
S3_BUCKET: console-data
labels:
# Routing is an Ingress object served by Hanzo Ingress (HIP-0068),
# not per-container labels.
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
interval: 30s
timeout: 10s
retries: 3
console-worker:
image: ghcr.io/hanzoai/console-worker:latest
environment:
DATABASE_URL: ${DATABASE_URL}
CLICKHOUSE_URL: ${CLICKHOUSE_URL}
depends_on:
console-web:
condition: service_healthy
On first startup, Console executes the following bootstrap sequence:
1. Connect to the store and run migrations
2. Connect to ClickHouse and initialize analytics schema
3. Read HANZO_INIT_ORG_IDS environment variable
4. For each org ID:
a. Call IAM GET /v1/iam/get-organization/{orgId}
b. If not found: Call IAM POST /v1/iam/add-organization
c. If found: Call IAM POST /v1/iam/update-organization (upsert)
5. Read HANZO_INIT_USER_EMAIL
a. Call IAM GET /v1/iam/get-user to find or create user
b. Grant OWNER membership for all initialized orgs
6. Read HANZO_INIT_PROJECT_ORG_ID
a. Create default project in specified org
b. Generate initial API key pair
7. Log bootstrap summary and start serving
If HANZO_INIT_USER_EMAIL matches an existing user (even one created through a different path, e.g., Git-provider login on Platform), the bootstrap grants membership without requiring a password. This prevents duplicate identity silos.
Console uses NextAuth.js with a custom IAM provider:
1. User visits console.hanzo.ai
2. NextAuth redirects to hanzo.id/oauth/authorize
?client_id=hanzo-console-client-id
&redirect_uri=console.hanzo.ai/api/auth/callback/hanzo-iam
&scope=openid profile email
3. User authenticates at hanzo.id
4. IAM redirects back with authorization code
5. Console exchanges code for tokens via IAM token endpoint
6. Console validates JWT, checks admin role
7. Session created, 30-minute idle timeout
Only users with OWNER or ADMIN role in at least one organization can access Console. MEMBER-only users are redirected to Cloud.
# Clone repository
git clone https://github.com/hanzoai/console
cd console
# Install dependencies
pnpm install
# Start infrastructure (the shared tier, locally: sql, datastore, kv, s3)
pnpm run infra:dev:up
# Initialize database
pnpm --filter=shared run db:reset
pnpm --filter=shared run ch:reset
pnpm --filter=shared run db:seed:examples
# Start development server
pnpm run dev:web # http://localhost:3000
# Development credentials
# Email: [email protected]
# Password: password
Console proxies administrative requests to backend services. The proxy layer strips client-supplied tenant headers and replaces them with server-side session-derived values to prevent header injection attacks:
// Proxy tenant header construction (simplified)
function buildProxyTenantHeaders(session: Session): Headers {
// Start with EMPTY headers -- never trust client-supplied values
const headers: Headers = {};
// Only use server-verified session context
if (session.orgId) {
headers["x-org-id"] = session.orgId;
headers["x-tenant-id"] = session.orgId;
}
if (session.projectId) {
headers["x-project-id"] = session.projectId;
}
headers["x-actor-id"] = session.userId;
return headers;
}
Proxy routes exist for:
/v1/iam/* -- IAM administrative operations/v1/kms/* -- KMS secret management/v1/agents/* -- agent orchestration/v1/compute/* -- compute resource managementThe console does not stand up a proxy prefix of its own. api.hanzo.ai is the endpoint and the path starts at /v1/; a /api/proxy/... tree is a second address for a surface that already has one (HIP-0119, HIP-0128).
Console enforces the strictest access controls in the Hanzo ecosystem:
protectedOrganizationProcedure middleware.protectedProjectProcedure middleware ensures project-level operations are authorized.x-org-id, x-project-id, x-tenant-id) are constructed exclusively from server-side session data. Client-supplied tenant headers are stripped before proxying. This prevents a compromised client from accessing another organization's data.Production Console supports IP-based access restriction:
# Per-organization IP allowlist
CONSOLE_IP_ALLOWLIST_HANZO: "24.199.76.0/24,10.0.0.0/8"
CONSOLE_IP_ALLOWLIST_LUX: "24.144.69.0/24,10.0.0.0/8"
Requests from non-allowlisted IPs receive a 403 Forbidden response. This is enforced by Hanzo Ingress middleware (HIP-0068), before the request reaches the Console application.
Console requires MFA for all admin users. On first login, if a user does not have WebAuthn or TOTP configured, Console redirects them to hanzo.id to enroll a second factor before granting a session. This is enforced by checking the mfaEnabled claim in the IAM JWT.
All administrative actions generate immutable audit entries. The audit subsystem:
Console never stores secrets directly:
KMSSecret CRDs).Console's dependency supply chain is monitored:
pnpm lockfile integrity is verified in CI.pnpm overrides pin known-vulnerable transitive dependencies to patched versions.turbo prune for minimal attack surface.DOCKER_BUILD=1 disables Sentry integration and environment validation during build to prevent secret leakage into image layers.# Run synchronous tests
pnpm test-sync --testPathPatterns="admin"
# Run async tests
pnpm test -- --testPathPatterns="organization"
# Run against local infrastructure
pnpm run infra:dev:up
pnpm test -- --testPathPatterns="proxy"
# Fast typecheck across all packages
pnpm tc
# Full Next.js build (catches runtime type errors)
pnpm build:check
| Metric | Target | |--------|--------| | Page load (first contentful paint) | < 1.5s | | tRPC query response (P95) | < 200ms | | Trace list query (ClickHouse, 10M rows) | < 500ms | | Concurrent admin sessions | > 100 | | Uptime SLA | 99.9% |
Copyright and related rights waived via CC0.