This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Aether is a distributed control plane for routing structured messages, tracking tasks, and managing connection lifecycles. It coordinates external agents, tasks, and engines through a gRPC gateway backed by RabbitMQ Streams for messaging and Redis for state management.
This project is made by scitrera.ai. Therefore, the naming involves "scitrera"; NEVER "scittera" or any other similar typographical error variant.
Key architectural principle: The connection itself IS the distributed lock AND the heartbeat. No separate heartbeat API exists - connection liveness determines entity availability.
- api/ - Protobuf definitions and generated code (own Go module:
github.com/scitrera/aether/api) - sdk/ - Client SDKs (Go, Python, TypeScript)
- server/ - Go server (module:
github.com/scitrera/aether/server)- server/cmd/ - Main entry points (gateway, aetherlite, auth-proxy, proxy-sidecar, workflow, migrate, cleanup, init-secrets, readiness-check, loadtest)
- server/configs/ - YAML configuration files (e.g., dev.yaml)
- server/deployments/ - Kubernetes manifests and Docker Compose files
- server/docs/ - Server documentation (quickstart, scaling, monitoring, admin API, error codes, etc.)
- server/internal/ - Core implementation packages
- server/migrations/ - PostgreSQL schema migrations (embedded, auto-run on startup)
- server/pkg/ - Shared models and utilities
- server/scripts/ - Server dev scripts (infra, test, certs, load test)
- server/specification.md - Full system specification (v4.0, reflects actual implementation)
- server/go.mod, server/go.sum - Go module files
- server/Dockerfile - Multi-stage build for gateway, cleanup, migrate, auth-proxy, init-secrets, workflow, aetherlite, and proxy-sidecar binaries
- server/Makefile - Build/test/run targets (run from server/ directory)
- scripts/ - Repo-wide scripts (compile-protos.py, update-versions.py — thin shims over scitrera-repo-tools)
- refs/ - Reference materials (external open source code; exclude from general scans)
- .claude - Claude configuration files
- .slop - Directory to store random markdown files and notes that may be relevant but not necessarily
- CLAUDE.md - This file
cd server
go build -o gateway ./cmd/gatewaycd server
# With config file:
./gateway --config configs/dev.yaml
# With dev defaults (no config file required):
AETHER_ALLOW_DEV_MODE=true ./gateway --dev --insecure-admin
# Or run directly:
go run ./cmd/gatewaycd server
go test ./... # Run all tests
go test -short ./... # Skip integration tests
go test -v ./internal/gateway # Run specific package tests with verbose outputDriven by the proto: block in versions.yaml, which pins every compiler
(protoc, the Go plugins, grpcio-tools, @grpc/proto-loader). Those pins are
verified before anything is generated, so a toolchain mismatch fails with the
exact install command instead of producing artifacts whose embedded version
headers drift from CI. --check is what proto-check.yml runs on PRs.
python scripts/compile-protos.py # regenerate Go + Python + TypeScript
python scripts/compile-protos.py --check # verify only; exit 1 on drift
python scripts/compile-protos.py --lang go # one language (repeatable)Requires protoc and the Go plugins on PATH (gofmt too, from the Go
toolchain), plus a virtualenv with the pinned grpcio-tools and
npm install in sdk/typescript. If repo-tools runs outside that virtualenv,
point it at the right interpreter with --python .venv/bin/python.
# Build context is the repo root
docker build -f server/Dockerfile -t scitrera/aether-gateway .cd server
# RabbitMQ with Streams plugin (ports 55552 stream, 55672 AMQP, 15672 management)
./scripts/docker_rmq_test.sh
# Redis / Valkey (ports 56379-56381 cluster)
./scripts/docker_valkey_test.sh
# PostgreSQL (optional — task/orchestration/ACL/audit features disabled without it)
# Use docker or a local instance; gateway connects via config postgres.* settings| Component | Location | Responsibility |
|---|---|---|
| Gateway Server | server/internal/gateway/server.go |
gRPC stream handling, auth, connection lifecycle, message routing, KV/checkpoint ops |
| Router | server/internal/router/router.go |
Topic-to-RabbitMQ-stream mapping, producer pool management, shared consumer fan-out |
| Session Registry | server/internal/state/session.go |
Redis SetNX-based distributed locks with TTL, session metadata |
| KV Store | server/internal/kv/store.go |
Hierarchical config store (global/workspace/user/user-workspace scopes) |
| Checkpoint Store | server/internal/checkpoint/store.go |
Persistent state checkpointing for agents/tasks (Redis-backed) |
| Task Store | server/pkg/tasks/store.go |
PostgreSQL-backed task lifecycle management |
| ACL Service | server/internal/acl/service.go |
RBAC with delegation chains for workspace access |
| Audit Logger | server/internal/audit/ |
Batched, configurable event capture (connection, auth, message, KV, admin, ACL) |
| Orchestration | server/internal/orchestration/ |
Task dispatch via AMQP, claim-based delivery, profile management |
| Admin Server | server/internal/admin/server.go |
REST API + embedded UI; ops server for health probes + Prometheus metrics |
| Auth Proxy | server/cmd/auth-proxy/ + server/internal/authproxy/ |
Standalone auth gateway for external services (e.g., MemoryLayer) |
| Identity Model | server/pkg/models/identity.go |
Eight principal types (Agent, Task, User, Service, Orchestrator, WorkflowEngine, MetricsBridge, Bridge), topic address derivation via ToTopic() |
| Prefix | Format | Description |
|---|---|---|
ag |
ag::{workspace}::{impl}::{spec} |
Specific agent instance |
tu |
tu::{workspace}::{impl}::{spec} |
Unique task (named) |
ta |
ta::{workspace}::{impl}::{id} |
Non-unique task instance (server-assigned ID) |
tb |
tb::{workspace}::{impl} |
Task broadcast (load-balancing) |
us |
us::{user_id}::{window_id} |
User window-specific |
uw |
uw::{user_id}::{workspace} |
User workspace-scoped |
uu |
uu::{user_id} |
User broadcast — reaches all of a user's windows regardless of active workspace; workspace-agnostic, ordinary (non-progress) messages. Platform-principal senders only (see permission matrix). |
ga |
ga::{workspace} |
Global agent broadcast |
gu |
gu::{workspace} |
Global user broadcast |
pg |
pg::{workspace} |
Progress updates (server-side recipient filtering) |
event:: |
Write: event::{workspace} (gateway rewrites to event::receiver{shard}); Subscribe (WE): event::receiver0 |
Workflow Engine fan-in; today 1 shard (event::receiver0). A legacy event.* (dot) form is rejected as an invalid topic prefix. |
metric:: |
Write: metric::{workspace} (gateway rewrites to metric::receiver{shard}); Subscribe (MB): metric::receiver0 |
Metrics Bridge fan-in; today 1 shard (metric::receiver0) |
tk |
tk::{workspace}::{task_id}::events (TaskEvent stream) and tk::{workspace}::{task_id}::msg (per-task chat) |
Per-task lanes; SUBJECT sessions auto-subscribe for the task's lifetime. Task-message lane uses full replay; user broadcast lanes (gu/uw/uu) and pg use resume-or-tail. |
br |
br::{impl}::{spec} |
Bridge (cross-workspace messaging integration) |
- Client opens gRPC stream and sends
InitConnectionwith principal type, identity, and credentials - Gateway authenticates (mTLS / task token / API key / OAuth)
- Gateway acquires distributed lock in Redis via
SetNXwith 30s TTL - If lock occupied → reject with
DuplicateIdentityError - ACL check verifies workspace access (before session becomes discoverable)
- Quota check atomically increments workspace connection count
- Session registered, client added to
activeStreamsandidentityIndex - Lock refresh goroutine starts (every 10s)
- Subscribe to appropriate topics based on principal type
- Send
ConnectionAck(with session ID for reconnection) +ConfigSnapshot(KV) - Enter main message loop until disconnect
- On disconnect: unsubscribe, release lock, decrement quota, update task state, audit
When a message targets an offline agent (ag.*) or unique task (tu.*):
- Gateway checks
identityIndex(local O(1)) then Redis lock (distributed) - If offline: message is published to RabbitMQ stream (persisted) AND orchestration task created
- Dispatcher publishes task notification to AMQP queue
- One gateway claims the task atomically and sends
TaskAssignmentto a connected orchestrator - Orchestrator spins up compute with a short-lived auth token
- Target connects, validates token, receives persisted messages via offset replay
All communication happens over a single bidirectional gRPC stream defined in api/proto/aether.proto:
- Upstream (client → server): InitConnection, SendMessage, SwitchWorkspace, KVOperation, CheckpointOperation, CreateTaskRequest, ProgressReport, TaskQuery, TaskOperation
- Downstream (server → client): ConnectionAck, IncomingMessage, ConfigSnapshot, Signal, ErrorResponse, KVResponse, CheckpointResponse, TaskAssignment, TaskQueryResponse, TaskOperationResponse, ProgressUpdate
Message types: CHAT, CONTROL, TOOL_CALL, EVENT, METRIC
An active gRPC stream connection represents both the distributed lock for that identity AND its liveness proof. The lock has a 30-second TTL refreshed every 10 seconds. If the gateway crashes, locks auto-expire. Session resume is supported via atomic Lua script for lock takeover.
- Agents and Unique Tasks: Globally unique. Two clients cannot connect with the same identity.
- Non-unique Tasks: Multiple connections allowed. Each gets a server-generated ID and subscribes to both
ta.*(direct) andtb.*(broadcast) topics. - Users: Unique per window (
us::{user_id}::{window_id}), allowing multiple browser tabs.
| Sender | Can Send To | Cannot Send To |
|---|---|---|
| Agent/Task | Agents, Tasks, Users, Events, Metrics | Orchestrators, Progress |
| User | Agents, Tasks, Users | Events, Metrics, Progress |
| Workflow Engine | Everything | — |
| Metrics Bridge | Nothing (receive-only) | All |
| Orchestrator | Agent/Task topics only | Events, Metrics |
| Bridge | Everything (any workspace) | — |
Cross-workspace sends are blocked: workspace-scoped principals cannot target topics in other workspaces. Bridges are cross-workspace by design (no workspace component) and check ACL per-message against the target workspace.
User-broadcast (uu::{user_id}): A workspace-agnostic channel that reaches every one of a user's windows regardless of which workspace each window is viewing (the non-progress complement to pg::us::{user}). Because the topic carries no workspace segment, the workspace ACL cannot gate it, so authorization is by principal type: only Service, WorkflowEngine, and Bridge principals may publish (enforceTopicPermissions). Users, Agents, Tasks, and Orchestrators are denied and must reach a user via us::/uw::/progress or task ownership. Users subscribe to their own uu:: topic on connect.
Cross-workspace event/metric broadcast: Sending to event:: or metric:: topics in another workspace requires capability/event_broadcast or capability/metric_broadcast ACL permission. Sending to the sender's own native workspace is implicitly permitted.
Metric payloads are structured: METRIC messages must carry a Metric proto payload (fields: trace_id, entries [{name, kind, qty}], metadata, client_timestamp_ms). All entries are additive deltas; negative qty requires the capability/metric_credit ACL permission. See spec Section 4.5 for details and error codes.
Four scopes with Redis namespace isolation:
- Global (
kv:agent:{impl}.{spec}:global) — cross-workspace agent state - Workspace (
kv:agent:{impl}.{spec}:ws:{workspace}) — read-only for agents, managed by platform - User (
kv:agent:{impl}.{spec}:user:{user_id}) — per-user agent state - User-Workspace (
kv:agent:{impl}.{spec}:user:{user_id}:ws:{workspace}) — per-user per-workspace
Redis serves multiple purposes via a shared UniversalClient:
- Session Registry: Distributed locks and session metadata (30s TTL)
- KV Store: Namespace-scoped configuration with optional TTL
- Checkpoint Store: Persistent agent/task state
- Task Tokens: Short-lived orchestration auth tokens (24h TTL)
- Quota Counters: Per-workspace connection and message rate tracking
Supports single-node, cluster, and auto-detect modes.
The system uses RabbitMQ Streams, not classic queues. Streams provide:
- Persistent, replayable message logs
- Consumer offset tracking for at-least-once delivery
- Per-topic producer pools with health checks and idle eviction (5min)
- Shared consumers with local fan-out to reduce RabbitMQ connections
PostgreSQL is used for persistent features that gracefully degrade when unavailable:
- Task lifecycle management (create, assign, complete, fail, retry, purge)
- Orchestration profiles and queue management
- ACL rules and delegation chains
- API token storage (HMAC-SHA256 hashed)
- Audit log (batched writes with retention)
- Agent registry
Schema is managed by embedded migrations in server/migrations/ that auto-run on startup.
Both gateway and aetherlite export OpenTelemetry traces and metrics via OTLP gRPC (internal/tracing/: InitTracer, InitMeter, NewLogBridge), gated on the standard OTEL_EXPORTER_OTLP_ENDPOINT env var (no-op when unset; OTLP gRPC default port 4317). This is independent of the admin ops server's Prometheus /metrics endpoint (port 9090).
The cleanup service (internal/cleanup/, wired in both gateway and aetherlite) runs periodic sweeps: orphaned orchestrated_task_queue reconciliation (QueueReconcileInterval, default 5m), audit-log retention sweep, stale agent_startup / interactive-task TTL reapers, and a stale regular pool-task sweep (CancelStalePoolTasks). On single-node AetherLite these run leader-gated/single-node-direct.
- Stateless Design: All state in Redis and PostgreSQL. Gateway instances share nothing.
- Distributed Locking: Redis SetNX ensures identity uniqueness across all instances.
- Session Affinity: Load balancer uses ClientIP (K8s) or cookies (nginx) for reconnection.
- Multi-Gateway Orchestration: Task claims use PostgreSQL-backed atomic operations; only one gateway delivers each task.
- Graceful Failover: Lock TTLs expire (30s), clients reconnect, RabbitMQ preserves offsets.
- Kubernetes:
server/deployments/k8s/gateway/— Deployment, Service, Ingress, ConfigMap, cert-manager - Docker Compose:
server/deployments/docker-compose/multi-instance.yaml— 3 instances + nginx - Dockerfile:
server/Dockerfile— Multi-stage build, non-root user, ports 50051/9090/31880/8080 (build from repo root:docker build -f server/Dockerfile .)
- Go SDK (
sdk/go/) — Full-featured: all 6 principal types, KV, checkpoints, reconnection, TLS, Docker orchestrator - Python SDK (
sdk/python-client/) — Sync + async clients, multiprocess orchestrator - TypeScript SDK (
sdk/typescript/) — Agent/User clients, gRPC transport, auto-reconnect
The complete system specification is in server/specification.md (v4.0, derived from the running codebase). Key sections:
- Section 1: Core Concepts (Principal Types, Connection=Lock=Heartbeat)
- Section 4: Messaging Topology & Topics (topic schema and permission matrix)
- Section 5: Orchestration & Lazy Loading (task assignment flow)
- Section 6-7: KV Store and Checkpoint Store
- Section 9: Quotas & Rate Limiting
- Section 12: Horizontal Scaling
- Each aether server or "server group" represents a single tenant. Multi-tenancy is achieved by deploying separate server groups with their own PostgreSQL and Redis databases and leveraging RabbitMQ virtual hosts.
- Workspaces are logical namespaces within a tenant/server group, not separate deployments — ACL-based isolation is sufficient at the workspace level.