Commands Reference
Commands Reference¶
Commands are the core building blocks of Intelligence Packs. This reference covers everything you need to know about creating commands.
The @command Decorator¶
The @command decorator is the primary way to define commands:
from huitzo_sdk import command, Context
from pydantic import BaseModel
class MyArgs(BaseModel):
param1: str
param2: int = 10
@command("my-command", namespace="my-pack")
async def my_command(args: MyArgs, ctx: Context) -> dict:
"""Command description shown in help."""
return {"result": args.param1 * args.param2}
Decorator Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
Required | Command name (lowercase, hyphens allowed) |
namespace |
str |
Required | Pack namespace (typically pack-name) |
version |
str |
"1.0.0" |
Command version (semver) |
timeout |
int |
60 |
Max execution time in seconds |
retries |
int |
3 |
Number of retry attempts |
retry_backoff |
float |
1.0 |
Exponential backoff base (seconds) |
retry_max_wait |
int |
60 |
Maximum wait between retries (seconds) |
queue |
str |
"default" |
Task queue: "fast", "medium", "long" |
output_format |
str |
"auto" |
Output format: "json", "text", "auto" |
description |
str |
From docstring | Override command description |
Retry Behavior: On failure, the SDK waits retry_backoff * (2 ^ attempt) seconds before retrying, capped at retry_max_wait. See Error Handling for details.
Example: Full Configuration¶
@command(
"analyze-data",
namespace="analytics",
version="2.1.0",
timeout=3600, # 1 hour max
retries=5, # 5 retry attempts
queue="long", # Long-running queue
output_format="json", # Always JSON output
description="Analyze large datasets with ML models"
)
async def analyze_data(args: AnalyzeArgs, ctx: Context) -> AnalysisResult:
...
Timeout Hierarchy¶
Huitzo uses a three-tier timeout system to ensure commands complete within reasonable bounds while giving flexibility at different levels:
Command Timeout ≤ Pack Timeout ≤ Platform Global Timeout
Timeout Levels¶
| Level | Defined By | Default | Description |
|---|---|---|---|
| Command | @command decorator |
60s |
Max time for this specific command |
| Pack | huitzo.yaml manifest |
3600s |
Must be ≥ max command timeout in pack |
| Platform | Admin configuration | 28800s (8h) |
Hard limit set by Huitzo (SaaS) or self-hosted admin |
How Timeouts Apply¶
- Command-level timeout is specified in the
@commanddecorator - Pack-level timeout in
huitzo.yamlmust be ≥ the maximum of all command timeouts - Platform timeout is the hard ceiling - commands cannot exceed this regardless of other settings
Example Configuration¶
# Command timeout: 300 seconds
@command("analyze", namespace="analytics", timeout=300)
async def analyze(args: Args, ctx: Context):
...
# huitzo.yaml - Pack timeout must be >= 300 (the max command timeout)
pack:
name: "analytics"
timeout: 600 # Pack-level ceiling
# Platform timeout (environment variable for self-hosted)
HUITZO_GLOBAL_TIMEOUT=28800 # 8 hours max
Validation Rules¶
- Pack build fails if any command timeout exceeds the pack timeout
- Command rejected at runtime if pack timeout exceeds platform global timeout
- Self-hosted admins can configure their platform global timeout via environment variables
Input Arguments (Pydantic Models)¶
All command inputs must be Pydantic models:
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List
from datetime import date
class ReportArgs(BaseModel):
"""Arguments for report generation."""
# Required field
report_type: str = Field(description="Type of report to generate")
# Optional with default
start_date: Optional[date] = Field(
default=None,
description="Start date for report"
)
# List field
departments: List[str] = Field(
default_factory=list,
description="Filter by departments"
)
# Field with constraints
max_rows: int = Field(
default=1000,
ge=1,
le=100000,
description="Maximum rows to include"
)
# Custom validation
@field_validator("report_type")
@classmethod
def validate_report_type(cls, v: str) -> str:
valid_types = ["sales", "inventory", "financial"]
if v not in valid_types:
raise ValueError(f"Must be one of: {valid_types}")
return v
Return Types¶
Security Constraint: Command return values are serialized to JSON and stored as plaintext in the platform database (
result_summarycolumn, truncated to 1024 bytes). Commands MUST NOT return sensitive data in their result dict. Prohibited return values include API keys, credentials, full document bodies, LLM response text, and PII. Return only metadata, identifiers, counts, and safe status values. Violating this constraint exposes user data on any database compromise (see Security Architecture).
Commands can return various types:
Dictionary (most common)¶
@command("get-stats", namespace="analytics")
async def get_stats(args: Args, ctx: Context) -> dict:
return {
"total": 1000,
"average": 45.5,
"trend": "up"
}
Pydantic Model¶
class StatsResult(BaseModel):
total: int
average: float
trend: str
@command("get-stats", namespace="analytics")
async def get_stats(args: Args, ctx: Context) -> StatsResult:
return StatsResult(total=1000, average=45.5, trend="up")
String¶
@command("greet", namespace="example")
async def greet(args: Args, ctx: Context) -> str:
return f"Hello, {args.name}!"
None (side effects only)¶
@command("send-notification", namespace="alerts")
async def send_notification(args: Args, ctx: Context) -> None:
await ctx.email.send(args.recipient, args.message)
# No return value
Sync vs Async Commands¶
Async (Recommended)¶
Use for I/O-bound operations:
@command("fetch-data", namespace="api")
async def fetch_data(args: Args, ctx: Context) -> dict:
# Async HTTP call
response = await ctx.http.get(args.url)
# Async storage
await ctx.storage.save("cached-data", response)
return response
Sync¶
Use for CPU-bound operations:
@command("compute-hash", namespace="crypto")
def compute_hash(args: Args, ctx: Context) -> str:
# CPU-intensive operation
return hashlib.sha256(args.data.encode()).hexdigest()
Class-Based Commands¶
For complex commands requiring lifecycle hooks:
from huitzo_sdk import HuitzoCommand
class ImportCommand(HuitzoCommand):
"""Import data from external source."""
name = "import"
namespace = "data"
version = "1.0.0"
def configure(self):
"""Called before execution to set options."""
self.timeout = 7200 # 2 hours
self.retries = 3
self.queue = "long"
def on_start(self, args: ImportArgs):
"""Called when command starts."""
self.log.info(f"Starting import from {args.source}")
def on_progress(self, percent: int, message: str):
"""Called to report progress."""
self.update_state(progress=percent, status=message)
def on_retry(self, attempt: int, error: Exception):
"""Called before each retry."""
self.log.warning(f"Retry {attempt}: {error}")
def on_complete(self, result):
"""Called after successful completion."""
self.log.info(f"Import complete: {result}")
def on_error(self, error: Exception):
"""Called on failure."""
self.log.error(f"Import failed: {error}")
# Optionally clean up resources
async def execute(self, args: ImportArgs, ctx: Context) -> dict:
"""Main execution logic."""
total_rows = 0
async for batch in self.fetch_batches(args.source):
await self.process_batch(batch)
total_rows += len(batch)
self.on_progress(
percent=int(total_rows / args.expected_rows * 100),
message=f"Processed {total_rows} rows"
)
return {"rows_imported": total_rows}
Error Handling¶
Built-in Exceptions¶
from huitzo_sdk.errors import (
CommandError, # General command failure
ValidationError, # Input validation failed
IntegrationError, # External service failed
RateLimitError, # Rate limited
)
@command("example", namespace="demo")
async def example(args: Args, ctx: Context) -> dict:
# Validation error
if args.count < 0:
raise ValidationError(
field="count",
value=args.count,
message="Count must be non-negative"
)
# Integration error
try:
result = await external_api.call()
except APIError as e:
raise IntegrationError(
service="external-api",
status_code=e.status,
message=str(e)
)
return result
Custom Exit Codes¶
@command("check-status", namespace="health")
async def check_status(args: Args, ctx: Context) -> dict:
status = await check_system()
if status == "critical":
raise CommandError(
message="System is in critical state",
exit_code=2 # Custom exit code
)
return {"status": status}
Command Metadata¶
Access command metadata at runtime:
@command("self-aware", namespace="meta")
async def self_aware(args: Args, ctx: Context) -> dict:
return {
"command_name": ctx.command_name,
"namespace": ctx.namespace,
"version": ctx.command_version,
"correlation_id": ctx.correlation_id,
"user_id": str(ctx.user_id),
"tenant_id": str(ctx.tenant_id),
}
Best Practices¶
1. Keep Commands Focused¶
# ✅ Good: Single responsibility
@command("send-email", namespace="notifications")
async def send_email(args: EmailArgs, ctx: Context) -> dict:
...
# ❌ Bad: Doing too much
@command("send-everything", namespace="notifications")
async def send_everything(args: Args, ctx: Context) -> dict:
# Sending email, SMS, Slack, push notifications...
2. Use Meaningful Names¶
# ✅ Good: Verb-noun pattern
@command("generate-report", namespace="analytics")
@command("validate-data", namespace="etl")
@command("sync-inventory", namespace="warehouse")
# ❌ Bad: Vague names
@command("do-stuff", namespace="misc")
@command("process", namespace="data")
3. Document with Docstrings¶
@command("calculate-roi", namespace="finance")
async def calculate_roi(args: ROIArgs, ctx: Context) -> dict:
"""Calculate Return on Investment for a given period.
This command analyzes revenue and costs to compute ROI metrics.
Results are cached for 24 hours.
Examples:
calculate-roi --start-date 2024-01-01 --end-date 2024-12-31
calculate-roi --department sales --include-projections
"""
...
4. Handle Errors Gracefully¶
@command("safe-operation", namespace="utils")
async def safe_operation(args: Args, ctx: Context) -> dict:
try:
result = await risky_call()
return {"success": True, "data": result}
except SpecificError as e:
ctx.log.warning(f"Expected error: {e}")
return {"success": False, "error": str(e)}
except Exception as e:
ctx.log.error(f"Unexpected error: {e}")
raise CommandError(
message="Operation failed unexpectedly",
details={"error_type": type(e).__name__}
)
See Also¶
- Context Reference – Access storage, integrations, and metadata
- Storage Reference – Persist and retrieve data
- Integrations Reference – Use built-in services
- Error Handling – Exception hierarchy and retry policies
- SDK Overview – Introduction to the SDK
- Pack Manifest – Configure pack-level settings
- Configuration Reference – Environment variables and timeouts