Files
aethera/backend/AGENTS.md
Evan Reichard 9b77a473b7
All checks were successful
continuous-integration/drone/push Build is passing
chore(dev): run backend with air
2026-05-02 16:29:10 -04:00

4.2 KiB

Backend Agent Instructions

Stack

  • Go 1.25.5 (reichard.io/aethera)
  • air (backend hot reload for make dev)
  • cobra (CLI framework)
  • logrus (structured logging)
  • openai-go/v3 (OpenAI API client)
  • jsonschema-go (structured output schema generation)
  • testify (testing assertions)
  • golangci-lint (linting)

Commands

go build -o ./dist/aethera ./cmd   # Build binary
golangci-lint run                   # Lint
go test ./...                       # Run all tests

Architecture

Frontend assets are embedded at compile time via web/embed.go (//go:embed static/*). The make frontend target copies built frontend output into backend/web/static/ before the Go build.

Directory Layout

cmd/                    # CLI entry (main.go, config.go)
internal/
  api/                  # HTTP handlers, request/response types, streaming generation
  client/               # OpenAI-compatible API client (chat, images, structured output)
  server/               # HTTP server setup, routing, logging middleware
  store/                # Store interface + FileStore (JSON files) & InMemoryStore
  storage/              # Standalone file utilities (e.g. ListImages)
  types/                # Shared domain types (MessageStats)
pkg/
  ptr/                  # Generic pointer helpers (Of, DerefOrZero)
  slices/               # Generic slice helpers (Map, First, Last, FindFirst)
  values/               # Generic value helpers (FirstNonZero, CountNonZero)
web/                    # Embedded static assets (embed.go)

Key Patterns

  • Store interface (internal/store/interface.go): abstraction over settings + chat persistence. Two implementations:
    • FileStore — JSON files on disk (settings.json + chats/*.json). Used in production.
    • InMemoryStore — map-based, thread-safe with sync.RWMutex. Used in tests.
  • DI via constructors: api.New(store, dataDir, logger), client.NewClient(baseURL), store.NewFileStore(path)
  • Streaming: generationManager coordinates concurrent chat completions. One active generation per chat. Subscribers receive MessageChunk updates via channels over NDJSON (application/x-ndjson).
  • Client lazy init: api.getClient() creates the OpenAI client on first use from stored settings, invalidated on settings change.
  • Env vars: all prefixed AETHERA_ (e.g. AETHERA_PORT, AETHERA_DATA_DIR). CLI flags override env.

API Routes

Method Path Handler
GET /api/settings GetSettings
POST /api/settings PostSettings
GET /api/models GetModels
GET /api/images GetImages
POST /api/images PostImage
DELETE /api/images/{filename} DeleteImage
GET /api/chats GetChats
POST /api/chats PostChat
GET /api/chats/{chatId} GetChat
POST /api/chats/{chatId} PostChatMessage
DELETE /api/chats/{chatId} DeleteChat
GET /api/chats/{chatId}/stream GetChatStream
POST /api/chats/{chatId}/stop StopChatGeneration

Testing

  • Use testify (require for fatal, assert for non-fatal)
  • Test both success and error paths
  • Table-driven tests where appropriate
  • File-based storage tests use temp directories
  • Concurrency safety is verified

Non-Negotiables

  • No unhandled errors — always check err
  • No ignored linter warnings
  • No sensitive data in logs
  • No hardcoded paths — use path.Join
  • No unsafe file access — use filepath.Base
  • Don't skip tests or linting

Code Style

  • Tab indentation, PascalCase exports, camelCase internal
  • Error wrapping with context: fmt.Errorf("...: %w", err)
  • Sentinel errors for domain cases (ErrChatNotFound, ErrNilChatID)
  • Struct tags for JSON with omitempty
  • Log with context: log.WithField("key", val)
  • Clean up resources with defer