docs(code): add inline comments at agent identity trap zones

Clarify the agent_key vs UUID contract inline at the four highest-ROI
trap zones: the Loop struct identity fields, DomainEvent identity
fields, the parseUUID/parseUUIDOrNil store helpers, and the WS method
resolver helpers. Each comment explains the invariant and links to
docs/agent-identity-conventions.md so future readers can find the full
rules without archaeology. Comments only — no runtime behavior change.
This commit is contained in:
viettranx
2026-04-11 21:22:23 +07:00
parent bf771a12f4
commit ca7f7b4f24
4 changed files with 43 additions and 29 deletions
+11 -4
View File
@@ -68,10 +68,17 @@ type CacheInvalidateFunc func(agentID uuid.UUID, userID string)
// Loop is the agent execution loop for one agent instance.
// Think → Act → Observe cycle with tool execution.
type Loop struct {
id string
displayName string
agentUUID uuid.UUID // set for context propagation
tenantID uuid.UUID // agent's owning tenant
// id is the human-readable agent_key (e.g. "goctech-leader"). Use for logs,
// UI events, system prompt rendering, filesystem paths, and context keys.
// NEVER set on DB FK columns or DomainEvent.AgentID — those require UUID.
// See docs/agent-identity-conventions.md.
id string
displayName string
// agentUUID is the canonical DB primary key. Use for SQL WHERE/JOIN,
// DomainEvent.AgentID, OTel span attributes, and context propagation via
// store.WithAgentID. See docs/agent-identity-conventions.md.
agentUUID uuid.UUID
tenantID uuid.UUID // agent's owning tenant
agentType string // "open" or "predefined"
defaultTimezone string // system default timezone for bootstrap pre-fill
provider providers.Provider
+11 -5
View File
@@ -27,15 +27,21 @@ const (
)
// DomainEvent is a typed event with metadata for the consolidation pipeline.
//
// Identity invariant: TenantID and AgentID are string fields for legacy wire
// compatibility, but consumers parse them as UUIDs before touching the DB.
// Publishers MUST supply valid UUID strings — never agent_key or tenant_slug.
// The publish-time observer in validate_agent_id.go warns on drift.
// See docs/agent-identity-conventions.md.
type DomainEvent struct {
ID string // UUID v7 for ordering
ID string // UUID v7 for ordering
Type EventType
SourceID string // dedup key (e.g. session key, run ID)
TenantID string
AgentID string
SourceID string // dedup key (e.g. session key, run ID)
TenantID string // MUST be a valid UUID string — never tenant_slug
AgentID string // MUST be a valid UUID string — never agent_key
UserID string
Timestamp time.Time
Payload any // typed per EventType (see payload structs below)
Payload any // typed per EventType (see payload structs below)
}
// --- Typed payloads, one per EventType ---
+10 -9
View File
@@ -408,10 +408,10 @@ func (m *AgentLinksMethods) invalidateLinkAgentsByID(ctx context.Context, source
// --- helpers ---
// resolveAgentUUID resolves an agent identifier (either UUID or agent_key) to its
// canonical UUID via a DB lookup. Tenant-aware via store.TenantIDFromContext(ctx)
// inside agentStore.GetByID/GetByKey. Prefer resolveAgentUUIDCached for hot-path
// handlers to avoid an unnecessary DB roundtrip.
// resolveAgentUUID resolves an agent identifier (either UUID or agent_key) to
// its canonical UUID via a DB lookup. Tenant-aware via
// store.TenantIDFromContext(ctx) inside agentStore.GetByID/GetByKey. Prefer
// resolveAgentUUIDCached in hot-path handlers to avoid the extra DB roundtrip.
// See docs/agent-identity-conventions.md trap zone 5.
func resolveAgentUUID(ctx context.Context, agentStore store.AgentStore, keyOrID string) (uuid.UUID, error) {
if id, err := uuid.Parse(keyOrID); err == nil {
@@ -436,11 +436,12 @@ type agentUUIDProvider interface {
}
// resolveAgentUUIDCached is the cache-aware variant of resolveAgentUUID.
// Checks the router cache first when the input is an agent_key and the Loop is
// cached — avoids a DB roundtrip on the hot path. Falls back to DB lookup on
// cache miss or when the input is a UUID string (not cached under the raw UUID
// key post-Phase-2 canonicalization). If router is nil, delegates straight to
// resolveAgentUUID. See docs/agent-identity-conventions.md trap zone 5.
// Checks the router cache first when the input is an agent_key and the Loop
// is cached — avoids a DB roundtrip on the hot path. Falls back to DB lookup
// on cache miss or when the input is a UUID string (router cache keys are
// canonicalized to `tenantID:agentKey`, so UUID inputs never hit the cache).
// If router is nil, delegates straight to resolveAgentUUID.
// See docs/agent-identity-conventions.md trap zone 5 and section 8.
func resolveAgentUUIDCached(ctx context.Context, router *agent.Router, agentStore store.AgentStore, keyOrID string) (uuid.UUID, error) {
// Fast path: input is agent_key and the agent is cached in the router.
if router != nil {
+11 -11
View File
@@ -477,12 +477,12 @@ func (s *PGMemoryStore) Close() error { return nil }
// --- Helpers ---
// parseUUID returns the parsed UUID or a descriptive error.
// Use for every INSERT/UPDATE/UPSERT/DELETE/SELECT-WHERE path where silent nil
// would either corrupt data (FK constraints reject it, but callers see cryptic
// PG code 23503 instead of a clean Go error) OR hide bugs as empty reads /
// zero-row updates. Phase 4 migration target for all 57 CRITICAL sites.
// See docs/agent-identity-conventions.md (Phase 6).
// parseUUID returns the parsed UUID or a descriptive error. Use for every
// INSERT/UPDATE/UPSERT/DELETE and any SELECT WHERE where silent nil would
// either corrupt data or hide bugs as empty reads / zero-row updates. FK
// constraints reject bad writes at the driver layer, but errors there come
// back as cryptic PG 23503 — parseUUID catches them upstream with a clean
// Go error. See docs/agent-identity-conventions.md trap zone 3.
func parseUUID(s string) (uuid.UUID, error) {
id, err := uuid.Parse(s)
if err != nil {
@@ -492,11 +492,11 @@ func parseUUID(s string) (uuid.UUID, error) {
}
// parseUUIDOrNil returns the parsed UUID or uuid.Nil on failure, without
// raising an error. INTENTIONALLY silent — only use for read-only SELECT-WHERE
// paths where a no-match (empty result) is acceptable. Do NOT use for writes,
// updates, deletes, or any SELECT where empty result would hide a bug.
// Prefer parseUUID for any new code.
// See docs/agent-identity-conventions.md (Phase 6).
// raising an error. INTENTIONALLY silent — only acceptable on read-only
// SELECT WHERE paths where a no-match (empty result) is the correct
// semantics on bad input. Do NOT use for writes, updates, deletes, or any
// SELECT where an empty result would hide a bug. Prefer parseUUID for new
// code. See docs/agent-identity-conventions.md trap zone 3.
func parseUUIDOrNil(s string) uuid.UUID {
id, err := uuid.Parse(s)
if err != nil {