From Model to Production
From Model to Production¶
The Production Checklist¶
Moving from "it works on my laptop" to production reveals a hidden checklist most teams discover one painful failure at a time:
- How do requests execute? What handles retries? Timeouts?
- How do you isolate tenants? One customer sees another's data?
- Who authenticates requests? How do you validate JWTs?
- Where does data live? How do you prevent race conditions?
- How do you call LLMs? What if OpenAI is down?
- How do you send emails? Handle bounces? Rate limits?
- How do you deploy? Roll back? Version?
- How do you debug distributed failures?
Huitzo handles all of this out of the box. You write business logic. The platform handles infrastructure.
What Huitzo Provides¶
Execution & Runtime¶
Celery workers execute your commands with automatic queue routing: - Fast queue (<5s) — Quick lookups, status checks - Medium queue (<60s) — LLM calls, email sends - Long queue (>60s) — Batch processing, report generation
Timeout enforcement follows a hierarchy:
- Command-level: @command("task", timeout=120) — 2 minutes max
- Pack-level: huitzo.yaml default for all commands
- Platform-level: Global safety limit
Automatic retries with exponential backoff:
@command("flaky-api-call", namespace="integration", retries=5, retry_delay=2)
async def call_external_api(args: ApiArgs, ctx: Context) -> dict:
# Platform retries up to 5 times with exponential backoff
response = await ctx.http.get(args.url)
return {"status": "success", "data": response}
Correlation IDs trace requests across distributed execution:
- Every request has ctx.correlation_id
- Appears in all logs, errors, traces
- Debug multi-command workflows end-to-end
Multi-Tenant Isolation¶
Every request has identity — automatically injected by the platform:
@command("save-preference", namespace="user")
async def save_preference(args: PreferenceArgs, ctx: Context) -> dict:
# Identity available in every command
# - ctx.user_id: UUID of authenticated user
# - ctx.tenant_id: UUID of customer/organization
# - ctx.session_id: UUID of WebCLI or API session
# Storage is automatically scoped to tenant + user + pack
await ctx.storage.save("theme", args.theme_name)
return {
"user_id": str(ctx.user_id),
"tenant_id": str(ctx.tenant_id),
"saved": True
}
PostgreSQL Row-Level Security (RLS) enforces isolation at database level:
- One tenant's data is invisible to another
- Developer never writes WHERE tenant_id = ? queries
- Impossible to leak data across tenants (enforced by database, not application code)
Authentication & Authorization¶
JWT tokens validated by platform before your command executes:
- Every request to /api/v1/commands/{namespace}/{command} requires valid token
- Context injection provides ctx.user_id, ctx.tenant_id automatically
- Invalid tokens rejected before your code runs
Rate limiting per tenant per pack:
- Prevent abuse and runaway costs
- Configured in huitzo.yaml manifest
- Enforced by platform, not command code
No auth code in commands — the platform handles it:
@command("admin-action", namespace="management")
async def admin_action(args: AdminArgs, ctx: Context) -> dict:
# ctx.user_id is already authenticated by platform
# You handle business logic authorization:
user = await ctx.storage.get(f"user:{ctx.user_id}")
if user.get("role") != "admin":
raise CommandError("Insufficient permissions")
# Proceed with admin logic
return {"status": "completed"}
Storage & State¶
ctx.storage backed by PostgreSQL JSONB (Cloud/Self-Hosted) or SQLite (Edge future):
- Automatic tenant isolation via RLS
- No schemas, no migrations, no connection pools
- TTL support for cache-style data
# Save data (automatically scoped to tenant + user + pack)
await ctx.storage.save("preferences", {"theme": "dark"}, ttl=86400) # 24h TTL
# Retrieve data
prefs = await ctx.storage.get("preferences")
# List keys with prefix
keys = await ctx.storage.list("session:*")
# Delete data
await ctx.storage.delete("temp-data")
Key scoping: Storage keys are scoped as {tenant_id}:{pack_namespace}:{key} internally. You just use "preferences" — the platform handles the rest.
Integrations¶
LLM Integration¶
Provider-agnostic interface supports OpenAI, Anthropic, and more:
from pydantic import BaseModel
class RiskAnalysis(BaseModel):
risk_score: int # 0-100
reasoning: str
recommended_action: str
@command("analyze-risk", namespace="insurance")
async def analyze_risk(args: ClaimArgs, ctx: Context) -> dict:
# Structured output with Pydantic schema
analysis = await ctx.llm.complete(
prompt=f"Analyze insurance claim risk: {args.claim_text}",
schema=RiskAnalysis, # Enforces structured output
model="gpt-4o-mini"
)
return {
"claim_id": args.claim_id,
"risk_score": analysis.risk_score,
"reasoning": analysis.reasoning
}
Platform handles: - API key management (configured per tenant or platform-wide) - Rate limiting and retry logic - Provider failover (if configured) - Cost tracking per tenant
Email Integration¶
Send transactional emails without managing SMTP:
@command("send-welcome", namespace="onboarding")
async def send_welcome_email(args: WelcomeArgs, ctx: Context) -> dict:
await ctx.email.send(
to=args.user_email,
subject="Welcome to Acme Insurance",
body="Your account is ready...",
html_body="<h1>Welcome!</h1><p>Your account is ready...</p>"
)
return {"status": "sent", "recipient": args.user_email}
HTTP Integration¶
Call external APIs with built-in error handling:
@command("fetch-policy", namespace="integration")
async def fetch_policy_data(args: PolicyArgs, ctx: Context) -> dict:
# GET request with automatic retries
response = await ctx.http.get(
url=f"https://api.partner.com/policies/{args.policy_id}",
headers={"Authorization": f"Bearer {args.api_key}"}
)
return {"policy_data": response}
File Integration¶
Process files (CSV, Excel, PDF) with built-in readers:
@command("process-claims-csv", namespace="insurance")
async def process_claims_csv(args: FileArgs, ctx: Context) -> dict:
# Read CSV from storage or URL
rows = await ctx.files.read_csv(args.file_path)
processed = []
for row in rows:
# Process each claim
result = await ctx.llm.complete(
prompt=f"Categorize claim: {row['description']}",
model="gpt-4o-mini"
)
processed.append({"claim_id": row["id"], "category": result})
return {"processed_count": len(processed)}
Error Handling & Retries¶
Automatic retries with exponential backoff for transient failures:
@command("external-api", namespace="integration", retries=5, retry_delay=2)
async def call_api(args: ApiArgs, ctx: Context) -> dict:
# Platform retries up to 5 times:
# Attempt 1: immediate
# Attempt 2: +2s
# Attempt 3: +4s
# Attempt 4: +8s
# Attempt 5: +16s
response = await ctx.http.get(args.url)
return {"data": response}
Structured exception hierarchy for precise error handling:
from huitzo_sdk.errors import (
ValidationError, # Input validation failed
CommandError, # General command failure
TimeoutError, # Execution timeout
StorageError, # Storage operation failed
ExternalAPIError, # External API call failed
)
@command("validate-and-process", namespace="app")
async def process_data(args: DataArgs, ctx: Context) -> dict:
if args.amount < 0:
raise ValidationError("Amount must be positive")
try:
result = await ctx.http.post(args.api_url, json={"amount": args.amount})
except Exception as e:
raise ExternalAPIError(f"Failed to call API: {e}")
return {"result": result}
See Error Handling Reference for complete hierarchy.
API Endpoints¶
Every @command gets a REST endpoint automatically:
POST /api/v1/commands/{namespace}/{command}
Content-Type: application/json
Authorization: Bearer <jwt_token>
{
"claim_id": "CLM-12345",
"claim_text": "Vehicle collision on I-95..."
}
No routing code required. Define the command, and the endpoint exists.
OpenAPI documentation auto-generated:
- Visit /docs for interactive Swagger UI
- /openapi.json for machine-readable schema
- Pydantic models become request/response schemas automatically
WebCLI provides interactive terminal interface:
- cd @acme/insurance to navigate to pack
- run analyze-claim --claim-id=CLM-12345 to execute
- Tab completion, help text, argument validation built-in
Deployment Portability¶
Same command code works across deployment modes:
| Mode | Status | Infrastructure Owner | Data Location |
|---|---|---|---|
| CLOUD | Production | Huitzo (multi-tenant SaaS) | Huitzo cloud (AWS) |
| SELF_HOSTED | Production (Year 1) | You (your datacenter) | Your infrastructure |
| EDGE | Design (Year 3+) | You (air-gapped devices) | Offline, local SQLite |
Platform abstracts deployment differences. Developer writes once, deploys anywhere.
Edge mode (future): Same commands run offline on-device with SQLite + local LLMs. No code changes. This is infrastructure's job, not yours.
Development Workflow¶
No local infrastructure required:
# Start development (uses cloud sandbox)
huitzo pack dev
# Your code runs in Huitzo cloud sandbox
# - Auto-reloads on file changes
# - Full PostgreSQL + Redis available
# - LLM, email, HTTP integrations work
# - Logs stream to terminal
# - Local proxy on localhost:8080
# - Docs on localhost:8124
Testing:
# Run pytest suite
huitzo pack test
# Run specific test file
huitzo pack test tests/test_analyze_claim.py
# Mock Context for unit tests
from huitzo_sdk.testing import MockContext
async def test_command():
ctx = MockContext(user_id="test-user", tenant_id="test-tenant")
result = await analyze_claim(args, ctx)
assert result["status"] == "analyzed"
Build and publish:
# Package pack for distribution
huitzo pack build
# Publish to registry under your @scope
huitzo pack publish
See Developer Environment Guide for complete setup.
What You Still Control¶
You retain full control over:
| Your Responsibility | Examples |
|---|---|
| Business logic | LLM prompts, processing workflows, decision trees |
| Data validation | Pydantic models define input/output schemas |
| Permission checks | Business logic authorization within commands |
| External API choices | Which services to integrate, API endpoints to call |
| Error messages | User-facing output, error text, help documentation |
| Pack configuration | Timeouts, queues, service usage in huitzo.yaml |
Platform handles infrastructure. You handle intelligence.
The Bottom Line¶
| You Write | Platform Handles |
|---|---|
| Business logic (~30-750 lines) | Execution runtime (Celery workers) |
| Pydantic validation models | Multi-tenant isolation (PostgreSQL RLS) |
ctx.llm/storage/http calls |
Database (PostgreSQL + Redis) |
| Command return values | REST API endpoints + retries + timeouts |
Pack manifest (huitzo.yaml) |
Authentication + authorization |
| — | Deployment (Cloud/Self-Hosted/Edge) |
| — | Logging, metrics, correlation IDs |
| — | Scaling and worker pool management |
| — | Rate limiting per tenant |
| — | OpenAPI documentation |
Result: 2,000 lines of infrastructure → 30 lines of business logic.
That's the Operating System for Intelligence.
See Also¶
- Architecture Overview – Technical architecture deep dive
- SDK Context API – Complete
ctxreference - Deployment Modes – Cloud, Self-Hosted, Edge details
- Self-Hosting Guide – Deploy on your infrastructure