Files
oO/docs/architecture/data-model.md
alvis d454a0a8bf docs: ADR-0014 — unified Profile model + agent registry
Propose a shared substrate for per-user prefs, contexts, per-key
consents, and per-agent state so adding an agent stays a manifest
change. Updates CLAUDE.md, README, and architecture docs to reflect
the multi-agent pipeline (ADR-0013) and the registry direction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 10:19:07 +00:00

4.2 KiB

Data model

Durable entities across modules. Per-module databases/schemas own these; cross-module access is only via the module's API.

Core entities

User                 auth + profile
  id (uuid)
  created_at
  email                        (from IdP)
  preferred_name?
  deleted_at?                  soft-delete for 30-day recovery; hard-delete after

IdentityLink         auth
  user_id
  provider                     "google" | "apple"
  provider_sub                 subject from IdP
  created_at

Session              auth
  user_id
  sid (uuid)                   in JWT
  issued_at
  expires_at
  revoked_at?

User (extended)      profile                                ADR-0014
  + tone                       'direct' | 'gentle' | 'motivational'
  + tip_kinds_json             jsonb: allowed tip kinds (stable globals)

UserPreference       profile                                ADR-0014
  user_id, scope, key (pk)
  scope                        'orchestrator' | 'agent:<id>'
  value_json                   open-ended; agent validates against its pref_schema on read
  source                       'user' | 'inferred'           (inferred never overwrites user)
  updated_at

UserConsent          profile                                ADR-0014
  user_id, consent_key (pk)
  consent_key                  'data:todoist' | 'data:calendar' | 'agent:focus-area' | ...
  granted_at
  revoked_at?                  null = currently active

UserContext          profile                                ADR-0014
  user_id, name (pk)           'work' | 'home' | 'vacation' | user-named
  active                       manual toggle in M2; auto-inference per agent in #112-#116
  schedule_json?               optional: when this context is active
  created_at

AgentOutput          recommender                            ADR-0013
  id (pk)
  user_id
  agent_id                     e.g. 'overdue-task' (matches a manifest)
  prompt_text                  snippet for the orchestrator prompt
  signals_snapshot             jsonb: inputs the agent consumed
  computed_at, expires_at      computed_at + manifest.ttl_sec
  agent_version                bump to invalidate cached outputs on logic changes

Credential           integrations
  user_id
  provider                     "todoist" | "google_calendar" | ...
  ciphertext                   sealed-box over {access, refresh, scopes, expires_at}
  meta                         provider-specific (sync_token cursor for Todoist)
  created_at
  last_refreshed_at
  revoked_at?

Event                events
  event_id (ulid)
  user_id
  schema_version
  kind                         e.g. "signals.task.updated"
  occurred_at
  ingested_at
  payload                      protobuf bytes

TipInstance          recommender
  tip_id (ulid)
  user_id
  policy_name                  "v4-orchestrator" (ADR-0013) | legacy bandit names retained for history
  policy_version
  candidate_source             "todoist" | "advice.library" | "agent-orchestrator" | ...
  context_snapshot             jsonb: features + agent snippets seen at decision time
  tip                          jsonb: {kind,title,body,source,deep_link,meta}
  created_at
  shown_at?                    set when the client reports render
  reaction?                    "done" | "snooze" | "dismiss" | null
  reacted_at?
  delivery_id?                 fk if surfaced via notifier push

Delivery             notifier
  delivery_id
  user_id
  tip_id
  channel                      "webpush" | "apns" | "fcm" | "email"
  dispatched_at
  delivered_at?
  failure_reason?

Foreign-key discipline

There are no cross-module FKs. Each module owns its tables. References by id are soft; consistency is maintained by events (user-deleted → every module cascades its own cleanup).

Deletion

User.deleted_at set → a user.deletion_requested event goes out → each module soft-deletes its rows → after 30 days a scheduled job hard-deletes. Credentials are revoked at the provider (not just erased locally) on soft-delete. See privacy.md.

Replay and reproducibility

TipInstance.context_snapshot captures the exact features that produced the decision. This is what lets offline replay re-score historical tips against a new policy without touching the feature store.