Skip to main content

Agent Access relay

Agent Access is not the Evolu CRDT sync path. It is a separate /api/context relay path that must prove its own privacy boundary. The safe contract is:
Browser
  ├─ resolves the current Sync owner
  ├─ renders the same context text used by in-app AI
  ├─ encrypts that text with a dedicated Agent Context key
  ├─ signs the write with the Sync owner write key
  └─ POSTs the encrypted envelope to /api/context

Context gateway
  ├─ verifies the owner signature against the Evolu relay DB
  ├─ maps sha256(GETBASED_TOKEN) → ownerId
  ├─ stores ciphertext under ownerId/profileId quota
  └─ never receives the Agent Context key

Local MCP
  ├─ fetches ciphertext with GETBASED_TOKEN
  └─ decrypts locally with GETBASED_AGENT_CONTEXT_KEY
The user-facing setup path is generated by settings-agent-access-panel.js. It builds a private one-paste command:
curl -fsSL https://getbased.health/install.sh | bash -s -- connect <client> --setup 'gbsetup_v1_...'
Supported <client> values are hermes, openclaw, claude-code, claude-desktop, cursor, cline, and codex. The gbsetup_v1_... payload is base64url JSON containing the Agent Access token, Agent Context key, gateway URL, selected client, version, and creation timestamp. Installers and logs must redact the setup payload; never print the blob after --setup.

Capabilities

ValuePurposeMust not be used for
Sync owner write keyHMAC proof that the browser controls the Sync ownerEncrypting Agent Access context or leaving the browser
GETBASED_TOKENBearer authorization for reading from the relayAES key material or storage namespace
GETBASED_AGENT_CONTEXT_KEYLocal AES-GCM decryption key for MCP clientsRelay authorization or server-side storage lookup

Browser write shape

The context gateway keeps the legacy top-level context string field for compatibility. The string must contain encrypted-envelope JSON, not plaintext context.
{
  "ownerId": "owner-id-from-sync",
  "profileId": "profile-id",
  "timestamp": 1782290000000,
  "signature": "hex-hmac",
  "context": "{\"encryptedContext\":{\"version\":2,\"alg\":\"AES-256-GCM\",\"keyDerivation\":\"raw-256-bit-key\",\"keyId\":\"...\",\"iv\":\"...\",\"ciphertext\":\"...\"}}"
}
The write signature is:
HMAC-SHA256(
  owner.writeKey,
  "agent-context:{ownerId}:{timestamp}:{sha256(GETBASED_TOKEN)}:{profileId}:{sha256(context)}"
)
The relay rejects unsigned writes, stale timestamps, token-owner mismatches, profile-limit excess, token-limit excess, and owner quota excess.

Envelope shape

Version 2 envelopes use a random raw 256-bit Agent Context key. There is no v2 salt because no token/password KDF is involved.
{
  "encryptedContext": {
    "version": 2,
    "alg": "AES-256-GCM",
    "keyDerivation": "raw-256-bit-key",
    "keyId": "sha256-prefix-base64url",
    "iv": "base64",
    "ciphertext": "base64"
  }
}
AES-GCM additional authenticated data is:
getbased-agent-context-v2:{profileId}

MCP read/decrypt behavior

getbased-mcp fetches the relay with:
GET /api/context?profile={profileId}
Authorization: Bearer {GETBASED_TOKEN}
Then it parses data.context, detects encryptedContext.version === 2, and decrypts locally with GETBASED_AGENT_CONTEXT_KEY. Wrong or missing keys must fail closed. The MCP may retain legacy plaintext/v1 decrypt compatibility only for rollout, not as the expected path.

Wearable daily-series section

Agent Access always receives the compact wearable summary when wearable context is enabled. The Settings → Agent Access panel can also sync a daily-series window: off, 7 days, 30 days, or 90 days. That preference is stored with the profile as agentAccessWearableSeriesDays and travels through encrypted Sync. When enabled, the browser pushes a separate machine-readable section for MCP clients, shaped as wearables-series-{N}d: one metric per line, daily values oldest → newest, for missing days, and source labels in parentheses. The hosted relay still stores only the encrypted context envelope; the MCP decrypts and exposes the section locally.

Clean-slate deployment note

If an old context gateway ever stored plaintext summaries, do not migrate those files into the owner-bound layout. Delete the legacy context store and require fresh browser pushes from updated clients. The fixed relay is intentionally fail-closed for old clients that do not send owner proof.

Verification gates

Before marking Agent Access ready:
  1. Run relay unit tests for signature validation, stale timestamp rejection, owner quota, profile limits, and token limits.
  2. Run browser/app tests proving the POST body contains context: JSON.stringify({ encryptedContext }), not plaintext.
  3. Run MCP tests proving v2 decrypt works with GETBASED_AGENT_CONTEXT_KEY and wrong-key/token-alone decrypt fails.
  4. Run a live relay E2E against the deployed gateway:
    • insert or create a disposable owner;
    • POST an encrypted sentinel;
    • fetch the relay payload directly;
    • prove the sentinel is absent from the serialized relay response;
    • prove the envelope has version: 2, keyDerivation: raw-256-bit-key, iv, ciphertext, keyId, and no salt;
    • prove getbased-mcp decrypts locally with the right key;
    • prove wrong key fails closed;
    • delete/revoke the test token mapping.
Good production E2E summary shape:
{
  "postStatus": 200,
  "fetchStatus": 200,
  "relayPlaintextAbsent": true,
  "relayHasEncryptedEnvelope": true,
  "envelopeVersion": 2,
  "keyDerivation": "raw-256-bit-key",
  "hasSalt": false,
  "mcpDecryptContainsSentinel": true,
  "mcpWrongKeyFailsClosed": true
}