NexusLinkNexusLink Docs

management/store — persistence for agent-network entities

Risk level: Medium — six brand-new tables behind AutoMigrate, one upsert-counter table that runs on the request hot path, and one column carrying an encrypted secret. Backward-compat impact: Additive (six new tables created by AutoMigrate; the Store interface gains 23 methods, but no existing column/index is touched).

Module boundary

This module is the persistence layer for the Agent Network feature. Everything the management server stores about LLM proxying — providers, policies, guardrails, the per-account settings row, a usage-counter table written on every proxied LLM request, and the account-budget rules — flows through the methods added to store.Store. The module owns six tables, six entity types from management/server/agentnetwork/types, and a single hot-path upsert (IncrementAgentNetworkConsumption) consumed by the proxy fleet.

Out of scope here: the catalog of provider definitions (compiled-in, no DB), the synthesizer/manager built on top of these CRUDs (covered in 21-management-agentnetwork.md), and the HTTP handlers that translate API requests into Save/Delete calls.

Files

Path Role
management/server/store/sql_store_agentnetwork.go gorm implementations of all 23 store methods
management/server/store/sql_store_agentnetwork_budgetrule_test.go round-trip + account-scoping coverage against a real sqlite store
management/server/store/sql_store.go one import, six entities appended to the AutoMigrate slice (sql_store.go:40, sql_store.go:141-142)
management/server/store/store.go 23 methods added to the Store interface (store.go:328-354)
management/server/store/store_mock_agentnetwork.go mockgen output for the new interface surface

Tables added / migrations

All six tables are created by db.AutoMigrate invoked from NewSqlStore at sql_store.go:133-143. There is no hand-rolled SQL migration script — the schema is whatever GORM derives from the struct tags.

CRUD surface added

Provider, Policy, Guardrail, BudgetRule follow the same pattern: Get<Kind>ByID, GetAccount<Kind> (list), Save<Kind> (upsert), Delete<Kind>, with account-scoping enforced by the existing accountAndIDQueryCondition / accountIDCondition constants (sql_store.go:59-62). Provider additionally exposes GetAllAgentNetworkProviders (cross-account, used by the synthesizer). Settings exposes Get/GetByCluster/Save (no delete — one row per account, created on first save). Consumption exposes the upsert Increment, a point Get, and a cross-window List.

Architecture & flow

flowchart LR
    handlers["HTTP handlers<br/>(management/server/agentnetwork)"] -->|Save/Delete| iface["Store interface<br/>store.go:328-354"]
    manager["agentnetwork.Manager"] -->|Get*| iface
    synth["synthesizer<br/>(global)"] -->|GetAllAgentNetworkProviders| iface
    proxy["proxy fleet<br/>(hot path)"] -->|IncrementAgentNetworkConsumption| iface
    iface --> sql["SqlStore methods<br/>sql_store_agentnetwork.go"]
    iface -.gomock.-> mock["MockStore<br/>store_mock_agentnetwork.go"]
    sql --> gorm["gorm.DB"]
    gorm --> tables[("6 tables<br/>agent_network_*")]
    sql --> enc["crypt.FieldEncrypt<br/>(provider only)"]

Reads decrypt provider secrets in-place; writes do provider.Copy().EncryptSensitiveData(...) before db.Save so the caller's in-memory object keeps the plaintext api_key (sql_store_agentnetwork.go:88-102). Every list/get takes a LockingStrength and applies clause.Locking{Strength: ...} when non-None — matching the rest of the store. The upsert path uses clause.OnConflict with gorm.Expr server-side increments so concurrent proxy nodes converge without read-modify-write races (sql_store_agentnetwork.go:321-335).

Invariants enforced at the store layer

Things to scrutinize

Correctness

Concurrency / transactions

Migration safety

Backward compatibility

Performance (indexes, N+1)

Test coverage

Test file Locks down
sql_store_agentnetwork_budgetrule_test.go::TestAgentNetworkBudgetRule_RealStore_RoundTrip full save → reload of AccountBudgetRule including the JSON-serialised PolicyLimits, target slices, double-delete returns NotFound (lines 18-59)
sql_store_agentnetwork_budgetrule_test.go::TestAgentNetworkBudgetRule_RealStore_ScopedByAccount cross-account isolation for budget rules (lines 63-78)
sql_store_agentnetwork_budgetrule_test.go::TestAgentNetworkSettings_RealStore_CollectionTogglesRoundTrip collection toggles default off, survive save/reload at the set values (lines 83-112)

Gap: there is no store-level test for providers (encryption round-trip), policies, guardrails, or IncrementAgentNetworkConsumption (concurrent upsert, window-key uniqueness). The consumption upsert is the most performance-sensitive method in this module and the only one without a real-sqlite test.

Known limitations / explicit non-goals

Cross-references