LICENSE-attested or repo-attested upstream is now called out at the top of LLM.md alongside the project description, so downstream audits (universe/docs/PRODUCTS.md) can rely on a single canonical location for the OSS lineage statement.
9.1 KiB
Hanzo Tasks
Durable workflow execution engine for AI agent orchestration.
Upstream: Temporal (MIT). Pre-v3.0 history is the Temporal fork; v3.0+ is native ZAP-only with the upstream gRPC/proto/runtime fully ripped out. Public Go module pinned at v1.x forever (semver-import-versioning policy).
Module
github.com/hanzoai/tasks
Versioning policy (2026-04-30)
- Stay on v1.x.x forever. Per global CLAUDE.md "NEVER bump Go packages above v1.x.x."
- Major version bump in semver-import-versioning would require module-path suffix (
/v2,/v3); we don't do that. - Old v2.x and v3.x tags (transition era) were deleted on 2026-04-30. Old v1.x tags (
v1.0.0–v1.42.0) are upstream-Temporal-era artifacts and remain in git history for blame. - New Hanzo-Tasks releases continue from
v1.43.0and bump minor for features, patch for fixes.
Quick Start
go build ./cmd/tasksd/
./tasksd start
Integration
- Playground connects to the Hanzo Tasks server via the durable-execution SDK
- Base embeds Tasks for durable cron/batch execution
- Each playground space = a Tasks namespace
- Each agent = a Tasks worker
Rebrand Notes (2026-03-19, updated 2026-04-26)
- Upstream server packages replaced with
github.com/hanzoai/tasksin all Go files, go.mod, go.sum cmd/serverrenamed tocmd/tasksd, binary name istasksd- Docker images:
ghcr.io/hanzoai/tasks - 2026-04-26: dropped compat.
temporal/→tasks/,temporaltest/→tasktests/,tests/(upstream-compat suite, 3.7 MB) deleted. No backward-compat shim remains.tests/testutils/retained (cert/TLS/IO helpers used by real unit tests).
Production Deployment (2026-03-19)
Live at tasks.hanzo.ai
- Cluster: hanzo-k8s (do-sfo3-hanzo-k8s), namespace
hanzo - Server:
ghcr.io/hanzoai/tasks:latest-- gRPC on 7233, HTTP on 7234 - UI:
ghcr.io/hanzoai/tasks-ui:latest-- port 8080 - Database: PostgreSQL at
sql.hanzo.svc:5432(databases:tasks,tasks_visibility) - Secrets: KMS-managed via
tasks-secrets(POSTGRES_PASSWORD, TASKS_AUTH_SECRET, IAM_CLIENT_SECRET)
Domains
| URL | Service | Protocol |
|---|---|---|
| tasks.hanzo.ai | tasks-ui (Web UI) | HTTPS |
| tasks-api.hanzo.ai | tasks (gRPC) | gRPC+TLS |
IAM Integration
- Provider: hanzo.id (OIDC)
- Client ID:
app-tasks - Callback:
https://tasks.hanzo.ai/auth/sso/callback - Scopes: openid, profile, email
- JWKS URI:
https://hanzo.id/.well-known/jwks(in-cluster:http://iam.hanzo.svc/.well-known/jwks) - Discovery:
https://hanzo.id/.well-known/openid-configuration - Registration script:
scripts/register-iam.sh
Auth Flow (two layers)
- UI (OIDC login): Tasks UI (
tasks-uicontainer) handles the browser OIDC flow. User visits tasks.hanzo.ai, gets redirected to hanzo.id for login, callback returns JWT tokens. Configured viaTASKS_AUTH_*env vars on the tasks-ui container. - Server (JWT validation): Tasks server validates the JWT bearer token on every gRPC/HTTP
request using JWKS keys fetched from hanzo.id. Configured via the wire-protocol env vars
below (legacy names preserved for upstream config compatibility). These env vars feed into
the embedded config template at
common/config/config_template_embedded.yaml->global.authorization.
Embedded Config Template Auth Env Vars (server)
Canonical
TASKS_*env var names. The binary reads these directly via the embedded config template parser.
| Env Var | Purpose | Value in K8s |
|---|---|---|
TASKS_JWT_KEY_SOURCE1 |
JWKS URI for key fetching | http://iam.hanzo.svc/.well-known/jwks |
TASKS_JWT_KEY_REFRESH |
Key refresh interval | 5m |
TASKS_AUTH_AUTHORIZER |
Authorizer type | default |
TASKS_AUTH_CLAIM_MAPPER |
Claim mapper type | default |
TASKS_JWT_PERMISSIONS_CLAIM |
JWT claim for permissions | permissions |
UI Auth Env Vars (tasks-ui container)
| Env Var | Purpose | Value in K8s |
|---|---|---|
TASKS_AUTH_ENABLED |
Enable OIDC login | true |
TASKS_AUTH_PROVIDER_URL |
OIDC issuer | https://hanzo.id |
TASKS_AUTH_CLIENT_ID |
OIDC client ID | app-tasks |
TASKS_AUTH_CLIENT_SECRET |
OIDC client secret | (from tasks-secrets) |
TASKS_AUTH_CALLBACK_URL |
OIDC callback | https://tasks.hanzo.ai/auth/sso/callback |
TASKS_AUTH_SCOPES |
OIDC scopes | openid,profile,email |
Namespace-to-Org Mapping
Tasks namespaces map 1:1 to Hanzo orgs. Users see only namespaces matching their IAM
org memberships. The JWT permissions claim carries namespace:role pairs (e.g.,
hanzo:admin, lux:read). The default claim mapper parses these into the internal
permission model.
K8s Manifests
- Canonical source:
k8s/in this repo - Also mirrored in:
universe/infra/k8s/tasks/ - Apply with:
kubectl apply -k k8s/ - DB init (one-time):
kubectl apply -f k8s/init-db.yaml
CI/CD
- GitHub Actions:
.github/workflows/build-and-publish.yml - Pushes to
ghcr.io/hanzoai/tasks:{sha,branch,latest}on main/release branches - Uses docker-bake.hcl for multi-arch builds (linux/amd64, linux/arm64)
Observability
- OTEL traces:
otel-collector.hanzo.svc:4318 - Insights analytics:
insights-capture.hanzo.svc:3000 - Dynamic config:
/etc/tasks/dynamic-config/dynamic-config.yaml(ConfigMap)
Native ZAP-only (2026-04-26)
The temporal fork is GONE. Zero go.temporal.io/*. Zero
google.golang.org/grpc. Zero protobuf on the wire. The whole binary is:
cmd/tasksd # 100 lines: signal handling, ZAP node, HTTP server
pkg/tasks/ # in-process server (zap.Node + opcode dispatch)
pkg/sdk/ # client + worker + workflow + activity + converter
ui/ # embedded React SPA (Vite bundle)
schema/tasks.zap # canonical wire schema
Build proof: go build ./cmd/tasksd → 10 MB native binary, 208 deps.
Boot proof: tasksd --zap-port 9999 --http :7243 listens on both,
serves /healthz, /v1/tasks/health, /_/tasks/* (UI), responds to
ZAP opcodes 0x0050–0x00A5 from pkg/sdk/client.
Workflow execution semantics are NOT yet wired in the native server —
handlers return a 501 envelope (opcode 0x00XX: not yet implemented in native server). The native engine is the next build phase. The shape
is in place so callers depend on the API while the engine lands behind
it.
What was deleted (2026-04-26)
tasks/(renamed temporal/, the fork's runtime)tasktests/(renamed temporaltest/)service/(frontend / history / matching / worker — 817 files)chasm/(component state machine framework — 169 files)client/(legacy gRPC clients — 40 files)api/(local mirror of temporal protos — 114 files)proto/,tools/(codegen for temporal protos)cmd/tools/(genrpcwrappers, genrpcserverinterceptors, getproto, etc.)components/(nexusoperations, callbacks, dummy state machines)docker/(pre-native server containerization)schema/{cassandra,elasticsearch,mysql,postgresql,sqlite}/(DB driver schemas — embedded SQLite returns via pkg/tasks when persistence lands)- 40 tainted
common/subdirs + 7 top-level common files (util.go,rpc.go,rpc_mock.go,client_cache.go,daemon.go,constants.go) tests/(upstream-compat suite, 3.7 MB)
What remains in common/
Pure stdlib utilities that survive the rip: aggregate, auth, build,
channel, circuitbreaker, clock, collection, contextutil,
convert, debug, definition, effect, finalizer, future, goro,
health, masker, number, pingable, pprof, predicates, quotas,
resolver, routing, schedules, shuffle, stream_batcher, tasks,
tasktoken, timer, util, versioninfo. These are candidates for
further pruning once the native engine settles.
Auth: IAM only (v3.5.0+, 2026-04-29)
tasksd validates Authorization: Bearer <jwt> directly against IAM
JWKS — no gateway dependency required. The strip+mint pattern is the
trust boundary:
- Inbound
X-Org-Id/X-User-Id/X-User-Emailare unconditionally deleted on every request (auth.stripIdentityHeaders). - If a Bearer JWT is present, the token is parsed (RS256/ES256/...) and
verified against keys fetched from
TASKSD_JWKS_URL. Issuer and audience are checked againstTASKSD_JWT_ISSUER/TASKSD_JWT_AUDIENCE. - On success,
X-Org-Idis minted from theownerclaim,X-User-Idfromsub,X-User-Emailfromemail. Per-org store scoping usesengine.WithOrg(auth.OrgID(ctx)). TASKSD_REQUIRE_IDENTITY=true(production) rejects requests without a validated JWT. Default false keeps embedded/dev path functional.
Production env (do-sfo3-hanzo-k8s/hanzo):
TASKSD_JWKS_URL=http://iam.hanzo.svc/.well-known/jwks
TASKSD_JWT_ISSUER=https://hanzo.id
TASKSD_REQUIRE_IDENTITY=true
History — pre-v3.5.0, the middleware trusted client-supplied X-* headers
under the assumption that hanzoai/gateway sat between ingress and tasksd.
The actual ingress topology routed direct to the tasks Service, leaving
X-Org-Id spoofable on tasks-api.hanzo.ai. v3.5.0 makes tasksd
self-sufficient: gateway can still front it for rate limiting / billing,
but it is no longer the sole trust boundary.