Act on behalf of the user — one calendar tool, any provider¶
DNA's thesis is author once, DNA operates your digital life in any AI client. The
On-Behalf-Of guide showed DNA reading the signed-in user's Microsoft
365 calendar on their behalf. This guide is the next step: the same capability made
provider-agnostic through the ActOnBehalfPort, so "read my calendar" works the
same way whether the user signed in with Microsoft or Google — the model sees one
neutral calendar_list tool, and which backend runs is decided by who signed in.
The full design (and the ratified decisions) is ADR-act-on-behalf-port. This is the how-it-fits-together tour.
Status: PoC. The port, the Microsoft reference impl, the neutral
calendar_listadapter, and a stubbed Google skeleton are shipped and tested against fakes. A live Google integration (real OAuth consent + refresh storage, or Domain-Wide Delegation) is deferred — it needs a Google Cloud project, not more code.
The seam: two steps, one abstracted¶
"Read the user's calendar" splits into two steps, and the port abstracts only the first:
(A) acquire a user-scoped credential ← PROVIDER-SPECIFIC (OBO / OAuth / DWD)
(B) call "the calendar API" as that user ← COMMON, one capability adapter
ActOnBehalfPortis step (A):credential_for(ctx, capability, scopes)returns aUserCredential(a bearer + the provider's API base). Microsoft does this with an On-Behalf-Of token exchange; Google does it with a previously-consented OAuth refresh token. The mechanisms genuinely differ — the port abstracts the outcome, not Microsoft's mechanism.- The
calendar_listcapability adapter is step (B): it consumes theUserCredentialand never sees whether it came from OBO, OAuth, or DWD. It returns one neutral event shape ({count, events:[{id, subject, start, end, location, organizer, web_link}]}) whichever provider served it.
The asymmetry that proves it¶
ActContext.raw_token — the inbound bearer — is Optional. Microsoft OBO needs it
(it is the exchange assertion); Google auth-code/DWD do not (they use a consented
refresh token or a self-signed service-account JWT). Honoring that asymmetry in the
contract is the concrete proof the port is not just "Microsoft OBO with a coat of
paint".
Identity → provider¶
DNA does not ask you to sign in again to pick a provider. The provider that verified
your token is the provider DNA acts through. The pluggable IdP layer already routes
each token to its provider; the port adds one label — a provider-family stamp
(entra → microsoft, google → google) — and the neutral tool reads it to select the
right ActOnBehalfPort. A Microsoft-signed-in user hits the Microsoft impl; a
Google-signed-in user hits the Google impl. An identity that maps to no configured
provider gets an honest "not available for you", never a crash.
Using it¶
The neutral tool is opt-in and gated exactly like the OBO tools: with the
graph: calendar group active it registers as calendar_list, alongside the
unchanged ms_calendar_list (now the Microsoft binding/alias).
// the model calls ONE tool, regardless of provider
{ "tool": "calendar_list", "arguments": { "top": 10 } }
- Signed in with Microsoft → the On-Behalf-Of exchange → Microsoft Graph.
- Signed in with Google (once wired) → the OAuth refresh exchange → Google Calendar.
- Either way, the same neutral events come back.
ms_calendar_liststill works for callers that want the explicit Microsoft binding.
What's deferred¶
The PoC proves the seam, not a production Google integration. Deferred: the real
Google OAuth consent flow + refresh-token storage, Domain-Wide Delegation, the
files / mail capabilities and all write scopes, multi-provider fan-out (one user,
both providers, merged results), the full per-provider config surface, and production
credential hardening. The dependency for going live is the per-provider trust setup
(an Entra app + consent; a Google OAuth client / Workspace super-admin) — not more
plumbing.