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.go defines Repository with 5 methods: Save, FindByID, FindAll, Delete, Count
- Current adapter:
pkg/infra/store/memory.go implements Repository with a map[string]*Environment + sync.RWMutex
- Service layer:
pkg/service/environment.go depends on the Repository interface (not the concrete store)
- Wiring:
cmd/waggle/main.go:59 instantiates store.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
Summary
Waggle currently uses an in-memory
MemoryStore(pkg/infra/store/memory.go) that implements theenvironment.Repositoryinterface. 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:
pkg/domain/environment/repository.godefinesRepositorywith 5 methods:Save,FindByID,FindAll,Delete,Countpkg/infra/store/memory.goimplementsRepositorywith amap[string]*Environment+sync.RWMutexpkg/service/environment.godepends on theRepositoryinterface (not the concrete store)cmd/waggle/main.go:59instantiatesstore.NewMemoryStore()and injects itThe service layer calls
Save()after every state transition (Creating, Running, Error, Destroying) andDelete()on destroy.FindByID()andFindAll()are read-only. All methods acceptcontext.Context.Critical contract: copy semantics
The
MemoryStorereturns copies on bothSave()andFindByID()to prevent aliasing bugs. The persistent store must maintain equivalent isolation — a mutated*Environmentreturned fromFindByID()must not affect the stored state untilSave()is called again.Approach
New adapter in
pkg/infra/store/Add a new file (e.g.,
bolt.goorsqlite.go) implementingenvironment.Repository. The implementation choice depends on requirements:For a single-binary MCP server, bbolt or modernc SQLite (pure Go) are good fits — no external database to manage.
Serialization
The
Environmentstruct needs to be serialized/deserialized. Options:Wiring change
Only
cmd/waggle/main.goneeds to change:Configuration
Add a
WAGGLE_STORE_PATHenv var (or reuseDataDir) for the database file path. TheMemoryStoreshould remain available for testing and as a fallback.Acceptance Criteria
Repositoryimplementation inpkg/infra/store/with persistent storageSave,FindByID,FindAll,Delete,Countenvironment.ErrNotFoundfor missing entities (matching MemoryStore contract)*Environmentis independent of stored state)MemoryStorekept for tests (not deleted)main.goupdated to use persistent store by defaultmemory_test.gocoveragedoc.gowith SPDX headers if new package created