-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Waggle currently uses an in-memory MemoryStore (pkg/infra/store/memory.go) that implements the environment.Repository interface. All environment state is lost on restart. We need a persistent store so environments can survive server restarts and support operational use cases like auditing.
Current Architecture (DDD)
The codebase follows Domain-Driven Design with clean separation:
- Domain interface:
pkg/domain/environment/repository.godefinesRepositorywith 5 methods:Save,FindByID,FindAll,Delete,Count - Current adapter:
pkg/infra/store/memory.goimplementsRepositorywith amap[string]*Environment+sync.RWMutex - Service layer:
pkg/service/environment.godepends on theRepositoryinterface (not the concrete store) - Wiring:
cmd/waggle/main.go:59instantiatesstore.NewMemoryStore()and injects it
The service layer calls Save() after every state transition (Creating, Running, Error, Destroying) and Delete() on destroy. FindByID() and FindAll() are read-only. All methods accept context.Context.
Critical contract: copy semantics
The MemoryStore returns copies on both Save() and FindByID() to prevent aliasing bugs. The persistent store must maintain equivalent isolation — a mutated *Environment returned from FindByID() must not affect the stored state until Save() is called again.
Approach
New adapter in pkg/infra/store/
Add a new file (e.g., bolt.go or sqlite.go) implementing environment.Repository. The implementation choice depends on requirements:
| Option | Pros | Cons |
|---|---|---|
| bbolt (embedded KV) | Zero config, single file, no external deps | No SQL, limited query flexibility |
| SQLite (embedded SQL) | SQL queries, mature, well-understood | CGO dependency (unless modernc) |
For a single-binary MCP server, bbolt or modernc SQLite (pure Go) are good fits — no external database to manage.
Serialization
The Environment struct needs to be serialized/deserialized. Options:
- JSON (simple, human-readable, already used in MCP responses)
- Protobuf (compact, schema-versioned — overkill for now)
Wiring change
Only cmd/waggle/main.go needs to change:
// Before
repo := store.NewMemoryStore()
// After (example with bbolt)
repo, err := store.NewBoltStore(cfg.DataDir + "/waggle.db")
if err != nil {
return fmt.Errorf("open store: %w", err)
}
defer repo.Close()Configuration
Add a WAGGLE_STORE_PATH env var (or reuse DataDir) for the database file path. The MemoryStore should remain available for testing and as a fallback.
Acceptance Criteria
- New
Repositoryimplementation inpkg/infra/store/with persistent storage - Implements all 5 methods:
Save,FindByID,FindAll,Delete,Count - Returns
environment.ErrNotFoundfor missing entities (matching MemoryStore contract) - Copy semantics maintained (returned
*Environmentis independent of stored state) - Context respected for cancellation/timeout on all operations
-
MemoryStorekept for tests (not deleted) - Wiring in
main.goupdated to use persistent store by default - Configuration via environment variable for store path
- Table-driven parallel unit tests matching
memory_test.gocoverage -
doc.gowith SPDX headers if new package created