Skip to content
Tech Stack

Tech Stack

Decisions locked 2026-09-22 after research (plans/reports/researcher-260922-0104-*.md). Versions verified against proxy.golang.org on that date.

Decisions

ConcernChoiceVersionRationale
LanguageGo1.27Stdlib uuid.NewV7, encoding/json/v2, GA testing/synctest, goroutine-leak profile.
HTTPStandard library net/httpGo 1.27http.ServeMux matches methods and path parameters (Go 1.22+), and r.Pattern gives the route for span names and http.route, so otelhttp works without a framework adapter. SSE flushing and cancellation on disconnect work unchanged. Groups, central errors and JSON binding are a small router in internal/server. goclaw does the same.
Streaming transportServer-Sent EventsstdlibClient POSTs, server streams. No bidirectional need. WebSocket rejected.
LLM provider (first)anthropic-sdk-go behind own Provider interfacev1.74.0SDK owns union decoding and stream accumulation. Learning stays on the loop. Interface is the seam for more providers.
LLM provider (second)openai-go behind same interfacev3.xCovers OpenAI, Ollama, OpenRouter, vLLM via base URL. Adapter synthesizes block start/stop.
DatabasePostgreSQL via pgxpgx v5.11.0Hand-written SQL, pgx.CollectRows. SQLite dual-dialect rejected as scope trap.
Migrationsgoose as library, embed.FSv3.28.0Embedded SQL migrations, one binary.
Tenancytenant_id NOT NULL on every table, scoped store built from contextn/aFail-closed. Cross-tenant read test per table. RLS with SET LOCAL as later backstop, never a replacement.
Identityhango owns tenants, users, roles, API keysn/aNo passwords or login UI. Bootstrap admin key. API keys belong to a user.
Run modelDurable runs row executed by scheduler lane worker, SSE subscribesn/aSurvives disconnect, enables cancel, resume, HITL, graceful drain. Single replica in v1.
MessagesAppend-only rows, seq number, jsonb blocksn/aPer-turn deltas, per-message addressing for compaction and subagents.
ObservabilityOpenTelemetry + log/slogotel v1.46.0hango-local GenAI semconv constants. LLM bodies off by default.
Rate limitinggolang.org/x/time/rate per API keylatestIn-process, documented single-replica limitation.
Testingtesting, testing/synctest, testcontainers-go for Postgresv0.44.0Real database in integration tests, no mocks of the store.
Lintgolangci-lint v2v2.13.2errcheck, govet, staticcheck, gosec, bodyclose, sqlclosecheck.
ContainerMulti-stage Dockerfile, distroless static nonrootn/aCGO_ENABLED=0, compose with Postgres.
Web UI (follow-up)Svelte 5n/aFor the web chat and the admin web page after the v1 phases. Both talk to the same HTTP API and SSE stream as any other client.

Explicitly rejected

  • Agent frameworks (eino, adk-go, genkit, langchaingo): the harness is the learning target.
  • Echo, Gin, chi, Fiber, gorilla: the standard-library router covers hango’s routes, and a framework context type would spread into every handler and middleware. Echo v5 also has no maintained OpenTelemetry middleware. Fiber’s fasthttp breaks SSE flushing, otelhttp and cancellation. chi is the fallback if the hand-written router outgrows about 200 lines, because its handlers stay plain net/http.
  • sqlc, ORMs: explicit SQL is the learning surface and where tenant scoping lives.
  • WebSocket, Redis, message queues: not needed at single-replica scope.
  • pkg/ directory: nothing public until an external consumer exists.