Files
oO/docs/adr/0015-data-source-consents.md
alvis 772bb6e194 feat(consents): auto-grant data:<provider> on connect; remove agent: consents (ADR-0015)
- integrations.ts: grant data:<provider> on OAuth callback, revoke on disconnect
- Backfill migration: INSERT OR IGNORE data:<provider> for all active tokens
- Agent manifests: drop agent:<id> from required_consents (momentum, time-of-day,
  overdue-task, recent-patterns, health-vitals) — per-agent control is a preference
- eligibility.ts: update comment to reflect data:-only consent model
- test_manifest.py: assert no agent: consents remain in any manifest
- migrations.test.ts: backfill idempotency tests for issue #127
- Dockerfile.api: drop --offline flag (fixes ERR_PNPM_NO_OFFLINE_META)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:09:58 +00:00

2.1 KiB

ADR-0015 — Data-source consents only; drop per-agent consent gate

Date: 2026-05-11
Status: Accepted
Supersedes: ADR-0014 §3 (consent model)

Context

ADR-0014 introduced required_consents on agent manifests. In practice two unrelated concepts were mixed into that field:

  • data:<source> — which data source the agent reads.
  • agent:<id> — whether the user opted into this specific agent.

No UI ever granted agent:<id> consents, so the eligibility filter at services/api/src/profile/eligibility.ts dropped every agent for every real user. The symptom was confirmed by MLflow trace tr-591449ea8a72af8e81b6a585234a86ab: user ODGp4Gkr7JWemMsqcMLMn had five fresh agent_outputs rows but the orchestrator received agent_ids: [].

Decision

Collapse to a single consent dimension: data source.

  1. required_consents entries must all start with data:. Agent manifests no longer list agent:<id> entries.
  2. Connecting a data source via the OAuth flow automatically grants data:<provider> in user_consents. Disconnecting sets revoked_at.
  3. data:core continues to be auto-granted on signup.
  4. Per-agent control becomes a preference (user_preferences[scope='agent:<id>', key='enabled']), not a consent. The eligibility filter already honours this — the only change is removing the agent:* consent check that was always failing.
  5. Eligibility rule (final): an agent is eligible iff every data:* it declares is granted and not revoked, no active context is in silenced_in_contexts, and the enabled preference is not false.

Consequences

  • Agents that only require data:core (time-of-day, momentum, recent-patterns) become eligible immediately after signup.
  • Agents requiring data:todoist or data:google-health become eligible as soon as the user connects the integration — no extra consent step.
  • A backfill migration grants data:<provider> for every existing active integration_tokens row, unblocking users who connected before this change.
  • ml/agents/tests/test_manifest.py asserts all required_consents start with data:, preventing regression.