# 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 ```bash 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`