| title |
|
||||||||
|---|---|---|---|---|---|---|---|---|---|
| description |
|
||||||||
| keywords |
|
||||||||
| topics |
|
||||||||
| tags |
|
||||||||
| content |
|
||||||||
| status | published |
One-time host setup: register an Azure application, ensure provider v2 is enabled on the OpenShell gateway, populate .env, then run bash scripts/bring-up.sh from the example root. The first bring-up runs an interactive Microsoft device-code login and registers the refresh token with the gateway. See the example README for the full bring-up flow.
The agent account is a dedicated Entra mailbox that you treat like a chatbot endpoint reachable over email. You don't sign in to it day to day — it exists so the agent has its own inbox to receive requests on and its own outbox to reply from.
- From your own mailbox, send an email to the agent account.
- The bridge picks the message up, passes the sender, subject, and body to the agent as a single prompt. Up to 5 emails handle concurrently.
- The agent replies in-thread via Microsoft Graph's reply API. End-to-end latency is typically ~30 seconds.
The agent uses delegated Microsoft Graph permissions — it acts as the agent account itself, not as an application with tenant-wide access. The gateway holds the refresh token; the sandbox sees only the OpenShell placeholder openshell:resolve:env:MS_GRAPH_ACCESS_TOKEN, which the L7 proxy substitutes at egress.
OUTLOOK_TARGET_MAILBOX is the agent account address; OUTLOOK_REPLY_TO is your own mailbox (used by outlook-email-search so the agent can read your mail when researching). Both are documented in .env.example.
- An Entra (Azure AD) tenant where you can register an application.
- A dedicated mailbox for the agent (e.g.
agt-you@yourcompany.com). - OpenShell ≥
v0.0.50withproviders_v2_enabled = trueat gateway scope:
$ openshell settings set --global --key providers_v2_enabled --value true --yesIn the Entra portal, create a new application registration:
- Supported account types: single-tenant (or whatever your org policy mandates).
- Redirect URIs: leave empty — device-code flow doesn't need one.
- Authentication → Allow public client flows: Yes (required for device code).
- API permissions → Microsoft Graph → Delegated permissions: add at minimum
Mail.ReadMail.SendMail.ReadWrite.Shared(only if you want the agent to read another user's mailbox via delegate access)offline_access(required to receive a refresh token)
- Grant admin consent for the tenant.
Note the Application (client) ID → OUTLOOK_CLIENT_ID and the Directory (tenant) ID → OUTLOOK_TENANT_ID.
OUTLOOK_TENANT_ID=<directory-tenant-id>
OUTLOOK_CLIENT_ID=<application-client-id>
OUTLOOK_TARGET_MAILBOX=agt-you@yourcompany.com
OUTLOOK_REPLY_TO=you@yourcompany.com
# OUTLOOK_ALLOWED_SENDERS=you@yourcompany.com,trusted-colleague@partner.com # optional$ bash scripts/bring-up.shWhen 02-providers.sh reaches the Outlook block, it invokes scripts/login-ms-graph.py, which prints:
Microsoft Graph device login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin
and enter the code ABCD1234 to authenticate.
Open that URL in a browser, sign in as the agent account (OUTLOOK_TARGET_MAILBOX), and enter the code. The script captures the refresh token and registers it with the gateway via openshell provider refresh configure. The token is cached at .bootstrap/cache/ms-graph-token.json (mode 0600; ignored by .gitignore) so subsequent bring-ups reuse it. Cache freshness is based on the refresh-token horizon, not the one-hour access-token expiry; the gateway rotates access tokens independently. Microsoft supplies the refresh lifetime when available, otherwise setup uses a conservative 90-day horizon. Legacy caches use their modification time for the same compatibility window. If rotation fails for a reused cache, setup requests device-code login only when OpenShell records an HTTP 400 token-endpoint rejection. Connectivity failures, server errors, and unknown failures stop setup without replacing the cache. A replacement cache is written only after the fresh credential rotates successfully. Set OUTLOOK_LOGIN_CACHE=0 to skip the on-disk cache entirely — see the security note below.
After this, the gateway auto-rotates the access token in the background. The sandbox bridge and skills call https://graph.microsoft.com directly with the placeholder header; the L7 proxy substitutes a live token on egress.
$ openshell provider refresh status hermes-direct-outlook --credential-key MS_GRAPH_ACCESS_TOKEN
$ openshell sandbox exec --name hermes-direct -- \
curl -sS -H "Authorization: Bearer openshell:resolve:env:MS_GRAPH_ACCESS_TOKEN" \
'https://graph.microsoft.com/v1.0/me' | head -c 300The /me call should return the agent account's profile JSON.
Microsoft caps unattended refresh-token lifetime around 90 days; the gateway rotates the refresh token transparently before then. If it does expire or gets revoked, re-authenticate by forcing a fresh device-code login:
$ OUTLOOK_LOGIN_CACHE=2 bash scripts/bring-up.shThis ignores the cached token at .bootstrap/cache/ms-graph-token.json, prompts you to re-authenticate, re-configures the provider's refresh material, and overwrites the cache with the fresh token.
After the device-code login succeeds, the refresh token lives in two places:
- Inside OpenShell's gateway credential store — encrypted at rest, registered via
provider refresh configure --secret-material-key refresh_token. This is the authoritative copy; OpenShell mints access tokens from it transparently. examples/recipes/nvidia/developer-community-chief-of-staff/.bootstrap/cache/ms-graph-token.jsonin the repo working tree — plaintext, mode 0600, used only to skip device-code re-auth on subsequentbash scripts/bring-up.shruns. The repo.gitignoreexcludes.bootstrap/sogit add -Awon't sweep it in; if you'd rather not have any on-disk cache at all, setOUTLOOK_LOGIN_CACHE=0. Lose this file and you'll just do one device-code login; nothing else changes.
If you'd prefer the refresh token never touch disk on your machine — shared workstation, demo environment, security-sensitive context — set OUTLOOK_LOGIN_CACHE=0 in your .env. Bring-up will run device-code login on every invocation and write nothing locally. The gateway-side encrypted copy still gets refreshed each time, so the sandbox itself is unaffected.
Microsoft's refresh tokens expire after roughly 90 days of inactivity; the gateway rotates the token transparently before then. If it does lapse, force a fresh device-code login with OUTLOOK_LOGIN_CACHE=2 bash scripts/bring-up.sh.
The outlook-email-search skill reads from OUTLOOK_REPLY_TO (your personal mailbox), not the agent's own inbox. For this to work over Graph, you must grant the agent account delegate access to your mailbox — in Outlook desktop: File → Account Settings → Delegate Access, or send a folder-share invitation that the agent account accepts. Without delegate access, Graph returns HTTP 403: Cannot find row based on condition. when the skill queries /users/$OUTLOOK_REPLY_TO/.
provider refresh rotatefails and refresh status reports HTTP 400 — the token endpoint rejected the cached credential. Setup requests device-code login automatically when it was reusing a cache. You can also re-runOUTLOOK_LOGIN_CACHE=2 bash scripts/bring-up.shto force login.provider refresh rotatefails during a gateway, network, TLS, or token-endpoint outage — setup stops and preserves the existing login cache. Resolve the transient failure and re-run bring-up; do not delete a still-valid cache.- Bridge logs
HTTP 401— the access token isn't being substituted. Confirmproviders_v2_enabledis set, thehermes-direct-outlookprovider exists (openshell provider get hermes-direct-outlook), and the sandbox was created after the provider (provider attachments are evaluated at sandbox create time). - Bridge logs
HTTP 403: Cannot find row based on condition— missing delegate access forOUTLOOK_REPLY_TO. See the section above. - Device-code prompt never appears —
scripts/02-providers.shshort-circuited because the cache file at.bootstrap/cache/ms-graph-token.jsonexists and is fresh. Force a re-prompt withOUTLOOK_LOGIN_CACHE=2(rewrites the cache) orOUTLOOK_LOGIN_CACHE=0(skip the cache for this run).
